fix(gazelle): Remove targets without srcs, even if they have an `imports` attribute. (#4283)

Before this PR,

```starlark
py_library(   # or py_binary or py_test or any alias_kind/map_kind
    name = "foo"
    imports = ["../.."],
)
```

Would not be removed by Gazelle. With this PR, it's now correctly
removed.

For PR review:
+ The first commit adds tests. If you're interested, you can check out
that commit and verify that tests fail.
+  2nd commit adds the fix and shows that the tests now pass
+  3rd commit is news.
+  any subsequent commits are followup to review comments.
diff --git a/gazelle/python/kinds.go b/gazelle/python/kinds.go
index dac271b..d0d4492 100644
--- a/gazelle/python/kinds.go
+++ b/gazelle/python/kinds.go
@@ -38,14 +38,13 @@
 		MatchAny:   false,
 		MatchAttrs: []string{"srcs"},
 		NonEmptyAttrs: map[string]bool{
-			"deps":    true,
-			"main":    true,
-			"srcs":    true,
-			"imports": true,
+			"deps": true,
+			"main": true,
+			"srcs": true,
 		},
 		SubstituteAttrs: map[string]bool{},
 		MergeableAttrs: map[string]bool{
-			"srcs": true,
+			"srcs":    true,
 			"imports": true,
 		},
 		ResolveAttrs: map[string]bool{
@@ -58,9 +57,8 @@
 		MatchAny:   false,
 		MatchAttrs: []string{"srcs"},
 		NonEmptyAttrs: map[string]bool{
-			"deps":    true,
-			"srcs":    true,
-			"imports": true,
+			"deps": true,
+			"srcs": true,
 		},
 		SubstituteAttrs: map[string]bool{},
 		MergeableAttrs: map[string]bool{
@@ -81,10 +79,9 @@
 	pyTestKind: {
 		MatchAny: false,
 		NonEmptyAttrs: map[string]bool{
-			"deps":    true,
-			"main":    true,
-			"srcs":    true,
-			"imports": true,
+			"deps": true,
+			"main": true,
+			"srcs": true,
 		},
 		SubstituteAttrs: map[string]bool{},
 		MergeableAttrs: map[string]bool{
diff --git a/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in b/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in
index 815e673..31c5ca9 100644
--- a/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in
+++ b/gazelle/python/testdata/remove_invalid_library_package_mode/BUILD.in
@@ -7,6 +7,13 @@
 )
 
 py_library(
+    name = "remove_invalid_library_with_imports_attrib",
+    srcs = ["__init__.py"],
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
+
+py_library(
     name = "deps_with_no_srcs_library",
     deps = [
         "//:remove_invalid_library",
diff --git a/gazelle/python/testdata/remove_invalid_per_file/BUILD.in b/gazelle/python/testdata/remove_invalid_per_file/BUILD.in
index d3b15a7..1288f9b 100644
--- a/gazelle/python/testdata/remove_invalid_per_file/BUILD.in
+++ b/gazelle/python/testdata/remove_invalid_per_file/BUILD.in
@@ -16,6 +16,13 @@
     visibility = ["//:__subpackages__"],
 )
 
+py_library(
+    name = "deleted_lib_with_imports_attrib",
+    srcs = ["deleted.py"],
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
+
 # Valid py_binary — __main__.py exists on disk, should be kept.
 py_binary(
     name = "remove_invalid_per_file_bin",
@@ -30,6 +37,13 @@
     visibility = ["//:__subpackages__"],
 )
 
+py_binary(
+    name = "deleted_bin_with_imports_attrib",
+    srcs = ["deleted_bin.py"],
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
+
 # Valid py_test — bar_test.py exists on disk, should be kept.
 py_test(
     name = "bar_test",
@@ -43,3 +57,10 @@
     srcs = ["deleted_test.py"],
     visibility = ["//:__subpackages__"],
 )
+
+py_test(
+    name = "deleted_test_with_imports_attrib",
+    srcs = ["deleted_test.py"],
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.in b/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.in
index e56ea16..e00849d 100644
--- a/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.in
+++ b/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.in
@@ -20,6 +20,15 @@
     visibility = ["//:__subpackages__"],
 )
 
+# TODO: Known limitation: not fully deleted until gazelle is bumped to include
+# https://github.com/bazel-contrib/bazel-gazelle/pull/2362
+my_py_library(
+    name = "deleted_lib_with_imports_attrib",
+    srcs = ["deleted2.py"],
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
+
 # Valid aliased py_test — bar_test.py exists on disk, should be kept.
 my_py_test(
     name = "bar_test",
diff --git a/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.out b/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.out
index 19d6c01..884d928 100644
--- a/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.out
+++ b/gazelle/python/testdata/remove_invalid_per_file/alias_kind/BUILD.out
@@ -19,6 +19,14 @@
     visibility = ["//:__subpackages__"],
 )
 
+# TODO: Known limitation: not fully deleted until gazelle is bumped to include
+# https://github.com/bazel-contrib/bazel-gazelle/pull/2362
+my_py_library(
+    name = "deleted_lib_with_imports_attrib",
+    imports = ["."],
+    visibility = ["//:__subpackages__"],
+)
+
 # Valid aliased py_test — bar_test.py exists on disk, should be kept.
 my_py_test(
     name = "bar_test",
diff --git a/gazelle/python/testdata/remove_invalid_per_file/map_kind/BUILD.in b/gazelle/python/testdata/remove_invalid_per_file/map_kind/BUILD.in
index 00b9a60..80b5899 100644
--- a/gazelle/python/testdata/remove_invalid_per_file/map_kind/BUILD.in
+++ b/gazelle/python/testdata/remove_invalid_per_file/map_kind/BUILD.in
@@ -18,12 +18,24 @@
     visibility = ["//:__subpackages__"],
 )
 
+py_library(
+    name = "deleted_lib_with_imports_attrib",
+    srcs = ["deleted.py"],
+    imports = [".."],
+    visibility = ["//:__subpackages__"],
+)
+
 # Stale mapped py_binary — deleted_bin.py does not exist, should be removed.
 my_py_binary(
     name = "deleted_bin",
     srcs = ["deleted_bin.py"],
 )
 
+my_py_binary(
+    name = "deleted_bin_with_imports_attrib",
+    srcs = ["deleted_bin.py"],
+)
+
 # Valid mapped py_test — bar_test.py exists on disk, should be kept.
 my_py_test(
     name = "bar_test",
@@ -35,3 +47,9 @@
     name = "deleted_test",
     srcs = ["deleted_test.py"],
 )
+
+my_py_test(
+    name = "deleted_test_with_imports_attrib",
+    srcs = ["deleted_test.py"],
+    imports = [".."],
+)
diff --git a/news/4282.fixed.md b/news/4282.fixed.md
new file mode 100644
index 0000000..d2ef1d9
--- /dev/null
+++ b/news/4282.fixed.md
@@ -0,0 +1,3 @@
+(gazelle) Fixed Python targets with an `imports` attribute not being deleted
+when their source files are removed
+([#4282](https://github.com/bazel-contrib/rules_python/issues/4282)).