fix: respect kind mapping (#1158)

When using the kind `gazelle:map_kind` directive, `gazelle` will
correctly generate the buildfile on the first pass (or if no target of
that type / name are present).

However, when running gazelle a second time (or if a target of the
mapped kind with the same name is present), `gazelle` will error out
saying that it kind create a target of the original kind because a
target of mapped kind is present and has the same name.

Ex:
Given the directive `# gazelle:map_kind py_test py_pytest_test
//src/bazel/rules/python:py_pytest_test.bzl`, `gazelle` will correctly
generate a `py_pytest_test` target where it would have generated a
`py_test` target.

But on a second invocation of `gazelle` (and subsequent invocations) it
will error our with:
```
gazelle: ERROR: failed to generate target "//test/python/common:common_test" of kind "py_test": a target of kind "py_pytest_test" with the same name already exists. Use the '# gazelle:python_test_naming_convention' directive to change the naming convention.
```
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index 26ffeda..fb41324 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -46,6 +46,13 @@
 	buildFilenames = []string{"BUILD", "BUILD.bazel"}
 )
 
+func GetActualKindName(kind string, args language.GenerateArgs) string {
+	if kindOverride, ok := args.Config.KindMap[kind]; ok {
+		return kindOverride.KindName
+	}
+	return kind
+}
+
 // GenerateRules extracts build metadata from source files in a directory.
 // GenerateRules is called in each directory where an update is requested
 // in depth-first post-order.
@@ -70,6 +77,10 @@
 		}
 	}
 
+	actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
+	actualPyLibraryKind := GetActualKindName(pyLibraryKind, args)
+	actualPyTestKind := GetActualKindName(pyTestKind, args)
+
 	pythonProjectRoot := cfg.PythonProjectRoot()
 
 	packageName := filepath.Base(args.Dir)
@@ -217,12 +228,12 @@
 		// generate it correctly.
 		if args.File != nil {
 			for _, t := range args.File.Rules {
-				if t.Name() == pyLibraryTargetName && t.Kind() != pyLibraryKind {
+				if t.Name() == pyLibraryTargetName && t.Kind() != actualPyLibraryKind {
 					fqTarget := label.New("", args.Rel, pyLibraryTargetName)
 					err := fmt.Errorf("failed to generate target %q of kind %q: "+
 						"a target of kind %q with the same name already exists. "+
 						"Use the '# gazelle:%s' directive to change the naming convention.",
-						fqTarget.String(), pyLibraryKind, t.Kind(), pythonconfig.LibraryNamingConvention)
+						fqTarget.String(), actualPyLibraryKind, t.Kind(), pythonconfig.LibraryNamingConvention)
 					collisionErrors.Add(err)
 				}
 			}
@@ -253,12 +264,12 @@
 		// generate it correctly.
 		if args.File != nil {
 			for _, t := range args.File.Rules {
-				if t.Name() == pyBinaryTargetName && t.Kind() != pyBinaryKind {
+				if t.Name() == pyBinaryTargetName && t.Kind() != actualPyBinaryKind {
 					fqTarget := label.New("", args.Rel, pyBinaryTargetName)
 					err := fmt.Errorf("failed to generate target %q of kind %q: "+
 						"a target of kind %q with the same name already exists. "+
 						"Use the '# gazelle:%s' directive to change the naming convention.",
-						fqTarget.String(), pyBinaryKind, t.Kind(), pythonconfig.BinaryNamingConvention)
+						fqTarget.String(), actualPyBinaryKind, t.Kind(), pythonconfig.BinaryNamingConvention)
 					collisionErrors.Add(err)
 				}
 			}
@@ -290,11 +301,11 @@
 		// generate it correctly.
 		if args.File != nil {
 			for _, t := range args.File.Rules {
-				if t.Name() == conftestTargetname && t.Kind() != pyLibraryKind {
+				if t.Name() == conftestTargetname && t.Kind() != actualPyLibraryKind {
 					fqTarget := label.New("", args.Rel, conftestTargetname)
 					err := fmt.Errorf("failed to generate target %q of kind %q: "+
 						"a target of kind %q with the same name already exists.",
-						fqTarget.String(), pyLibraryKind, t.Kind())
+						fqTarget.String(), actualPyLibraryKind, t.Kind())
 					collisionErrors.Add(err)
 				}
 			}
@@ -325,12 +336,12 @@
 		// generate it correctly.
 		if args.File != nil {
 			for _, t := range args.File.Rules {
-				if t.Name() == pyTestTargetName && t.Kind() != pyTestKind {
+				if t.Name() == pyTestTargetName && t.Kind() != actualPyTestKind {
 					fqTarget := label.New("", args.Rel, pyTestTargetName)
 					err := fmt.Errorf("failed to generate target %q of kind %q: "+
 						"a target of kind %q with the same name already exists. "+
 						"Use the '# gazelle:%s' directive to change the naming convention.",
-						fqTarget.String(), pyTestKind, t.Kind(), pythonconfig.TestNamingConvention)
+						fqTarget.String(), actualPyTestKind, t.Kind(), pythonconfig.TestNamingConvention)
 					collisionErrors.Add(err)
 				}
 			}
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.in b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
new file mode 100644
index 0000000..6a06737
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
@@ -0,0 +1,15 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+    name = "respect_kind_mapping",
+    srcs = ["__init__.py"],
+)
+
+my_test(
+    name = "respect_kind_mapping_test",
+    srcs = ["__test__.py"],
+    main = "__test__.py",
+    deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.out b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
new file mode 100644
index 0000000..7c5fb0b
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
@@ -0,0 +1,20 @@
+load(":mytest.bzl", "my_test")
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+    name = "respect_kind_mapping",
+    srcs = [
+        "__init__.py",
+        "foo.py",
+    ],
+    visibility = ["//:__subpackages__"],
+)
+
+my_test(
+    name = "respect_kind_mapping_test",
+    srcs = ["__test__.py"],
+    main = "__test__.py",
+    deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/README.md b/gazelle/python/testdata/respect_kind_mapping/README.md
new file mode 100644
index 0000000..9f0fa6c
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/README.md
@@ -0,0 +1,3 @@
+# Respect Kind Mapping
+
+This test case asserts that when using a kind mapping, gazelle will respect that mapping when parsing a BUILD file containing a mapped kind.
diff --git a/gazelle/python/testdata/respect_kind_mapping/WORKSPACE b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/respect_kind_mapping/__init__.py b/gazelle/python/testdata/respect_kind_mapping/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/respect_kind_mapping/__test__.py b/gazelle/python/testdata/respect_kind_mapping/__test__.py
new file mode 100644
index 0000000..2b180a5
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__test__.py
@@ -0,0 +1,26 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+from __init__ import foo
+
+
+class FooTest(unittest.TestCase):
+    def test_foo(self):
+        self.assertEqual("foo", foo())
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/gazelle/python/testdata/respect_kind_mapping/foo.py b/gazelle/python/testdata/respect_kind_mapping/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/foo.py
@@ -0,0 +1,16 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+def foo():
+    return "foo"
diff --git a/gazelle/python/testdata/respect_kind_mapping/test.yaml b/gazelle/python/testdata/respect_kind_mapping/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/test.yaml
@@ -0,0 +1,17 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+---
+expect:
+  exit_code: 0