fix: add missing `bzl_library` declarations to satisfy client `stardoc` usage (#33)

- Add missing `bzl_library` targets.
- Add `bzl_test` to ensure that the targets in this repository are
valid.

Closes #31.
diff --git a/BUILD.bazel b/BUILD.bazel
index e1ddbac..4dc6414 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,3 +1,5 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
 filegroup(
     name = "bzl_files",
     srcs = glob(["*.bzl"]) + [
@@ -9,3 +11,23 @@
 )
 
 exports_files(["features.bzl"])
+
+bzl_library(
+    name = "features",
+    srcs = ["features.bzl"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//private:util",
+        "@bazel_features_globals//:globals",
+    ],
+)
+
+bzl_library(
+    name = "deps",
+    srcs = ["deps.bzl"],
+    deps = [
+        "//private:repos",
+        "@bazel_tools//tools/build_defs/repo:http.bzl",
+        "@bazel_tools//tools/build_defs/repo:utils.bzl",
+    ],
+)
diff --git a/MODULE.bazel b/MODULE.bazel
index 61225ca..e34cddc 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -3,6 +3,14 @@
     version = "0.0.0",
 )
 
-version = use_extension("//private:extensions.bzl", "version_extension")
+bazel_dep(name = "bazel_skylib", version = "1.5.0")
 
+bazel_dep(
+    name = "stardoc",
+    version = "0.6.2",
+    dev_dependency = True,
+    repo_name = "io_bazel_stardoc",
+)
+
+version = use_extension("//private:extensions.bzl", "version_extension")
 use_repo(version, "bazel_features_globals", "bazel_features_version")
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index af29461..8325169 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -3,3 +3,38 @@
 load(":deps.bzl", "bazel_features_deps")
 
 bazel_features_deps()
+
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+
+bazel_skylib_workspace()
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+    name = "io_bazel_stardoc",
+    sha256 = "62bd2e60216b7a6fec3ac79341aa201e0956477e7c8f6ccc286f279ad1d96432",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz",
+        "https://github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz",
+    ],
+)
+
+load("@io_bazel_stardoc//:setup.bzl", "stardoc_repositories")
+
+stardoc_repositories()
+
+load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
+
+rules_jvm_external_deps()
+
+load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
+
+rules_jvm_external_setup()
+
+load("@io_bazel_stardoc//:deps.bzl", "stardoc_external_deps")
+
+stardoc_external_deps()
+
+load("@stardoc_maven//:defs.bzl", stardoc_pinned_maven_install = "pinned_maven_install")
+
+stardoc_pinned_maven_install()
diff --git a/deps.bzl b/deps.bzl
index 766638d..3ed76e1 100644
--- a/deps.bzl
+++ b/deps.bzl
@@ -1,10 +1,15 @@
-load("//private:version_repo.bzl", "version_repo")
-load("//private:globals.bzl", "GLOBALS")
-load("//private:globals_repo.bzl", "globals_repo")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//private:repos.bzl", "bazel_features_repos")
 
 def bazel_features_deps():
-    version_repo(name = "bazel_features_version")
-    globals_repo(
-        name = "bazel_features_globals",
-        globals = GLOBALS,
+    bazel_features_repos()
+    maybe(
+        http_archive,
+        name = "bazel_skylib",
+        sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
+        urls = [
+            "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
+            "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz",
+        ],
     )
diff --git a/features.bzl b/features.bzl
index fcc2abf..43657d1 100644
--- a/features.bzl
+++ b/features.bzl
@@ -28,16 +28,14 @@
     # Whether repository_ctx#download has the block parameter, allowing parallel downloads (#19674)
     download_has_block_param = ge("7.1.0"),
     # Whether repository_ctx#download has the headers parameter, allowing arbitrary headers (#17829)
-    download_has_headers_param = ge("7.1.0")
+    download_has_headers_param = ge("7.1.0"),
 )
 
 _flags = struct(
     # This flag was renamed in https://github.com/bazelbuild/bazel/pull/18313
     allow_unresolved_symlinks = (
-        "allow_unresolved_symlinks"
-        if ge("7.0.0-pre.20230628.2")
-        else "experimental_allow_unresolved_symlinks"
-    )
+        "allow_unresolved_symlinks" if ge("7.0.0-pre.20230628.2") else "experimental_allow_unresolved_symlinks"
+    ),
 )
 
 _rules = struct(
diff --git a/private/BUILD.bazel b/private/BUILD.bazel
index a61edcc..7cdf03d 100644
--- a/private/BUILD.bazel
+++ b/private/BUILD.bazel
@@ -1,5 +1,63 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
 filegroup(
     name = "bzl_files",
     srcs = glob(["*.bzl"]),
     visibility = ["//visibility:public"],
 )
+
+bzl_library(
+    name = "parse",
+    srcs = ["parse.bzl"],
+)
+
+bzl_library(
+    name = "util",
+    srcs = ["util.bzl"],
+    visibility = ["//:__subpackages__"],
+    deps = [
+        ":parse",
+        "@bazel_features_version//:version",
+    ],
+)
+
+bzl_library(
+    name = "globals",
+    srcs = ["globals.bzl"],
+    visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+    name = "globals_repo",
+    srcs = ["globals_repo.bzl"],
+    visibility = ["//:__subpackages__"],
+    deps = [
+        ":parse",
+    ],
+)
+
+bzl_library(
+    name = "version_repo",
+    srcs = ["version_repo.bzl"],
+    visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+    name = "repos",
+    srcs = ["repos.bzl"],
+    visibility = ["//:__subpackages__"],
+    deps = [
+        ":globals",
+        ":globals_repo",
+        ":version_repo",
+    ],
+)
+
+bzl_library(
+    name = "extensions",
+    srcs = ["extensions.bzl"],
+    visibility = ["//:__subpackages__"],
+    deps = [
+        ":repos",
+    ],
+)
diff --git a/private/extensions.bzl b/private/extensions.bzl
index 84040d0..7314d9f 100644
--- a/private/extensions.bzl
+++ b/private/extensions.bzl
@@ -1,8 +1,6 @@
-load("//:deps.bzl", "bazel_features_deps")
-load(":version_repo.bzl", "version_repo")
-load(":globals_repo.bzl", "globals_repo")
+load("//private:repos.bzl", "bazel_features_repos")
 
-def _version_extension_impl(mctx):
-    bazel_features_deps()
+def _version_extension_impl(_):
+    bazel_features_repos()
 
 version_extension = module_extension(_version_extension_impl)
diff --git a/private/globals_repo.bzl b/private/globals_repo.bzl
index 9487c80..fcadfb2 100644
--- a/private/globals_repo.bzl
+++ b/private/globals_repo.bzl
@@ -1,7 +1,21 @@
 load("//private:parse.bzl", "parse_version")
 
 def _globals_repo_impl(rctx):
-    rctx.file("BUILD.bazel", "exports_files([\"globals.bzl\"])")
+    rctx.file(
+        "BUILD.bazel",
+        """\
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+exports_files(["globals.bzl"])
+
+# This keeps stardoc happy if globals.bzl is pulled into a repository.
+bzl_library(
+    name = "globals",
+    srcs = ["globals.bzl"],
+    visibility = ["//visibility:public"],
+)
+""",
+    )
 
     bazel_version = parse_version(native.bazel_version)
 
diff --git a/private/repos.bzl b/private/repos.bzl
new file mode 100644
index 0000000..525452d
--- /dev/null
+++ b/private/repos.bzl
@@ -0,0 +1,10 @@
+load(":globals.bzl", "GLOBALS")
+load(":globals_repo.bzl", "globals_repo")
+load(":version_repo.bzl", "version_repo")
+
+def bazel_features_repos():
+    version_repo(name = "bazel_features_version")
+    globals_repo(
+        name = "bazel_features_globals",
+        globals = GLOBALS,
+    )
diff --git a/private/version_repo.bzl b/private/version_repo.bzl
index 105aea8..4cee444 100644
--- a/private/version_repo.bzl
+++ b/private/version_repo.bzl
@@ -1,5 +1,18 @@
 def _version_repo_impl(rctx):
-    rctx.file("BUILD.bazel", "exports_files([\"version.bzl\"])")
+    rctx.file(
+        "BUILD.bazel",
+        """
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+exports_files(["version.bzl"])
+
+bzl_library(
+    name = "version",
+    srcs = ["version.bzl"],
+    visibility = ["//visibility:public"],
+)
+""",
+    )
     rctx.file("version.bzl", "version = '" + native.bazel_version + "'")
 
 version_repo = repository_rule(
diff --git a/test/BUILD.bazel b/test/BUILD.bazel
index e3c572c..520ec56 100644
--- a/test/BUILD.bazel
+++ b/test/BUILD.bazel
@@ -1,3 +1,10 @@
+load(":bzl_test.bzl", "bzl_test")
 load(":test.bzl", "run_test")
 
 run_test(name = "tests")
+
+bzl_test(
+    name = "features_bzl_test",
+    src = "features_bzl_macro.bzl",
+    deps = ["//:features"],
+)
diff --git a/test/bzl_test.bzl b/test/bzl_test.bzl
new file mode 100644
index 0000000..42dc4da
--- /dev/null
+++ b/test/bzl_test.bzl
@@ -0,0 +1,42 @@
+"""Macro for Ensuring Starlark Dependencies are Specified Properly"""
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
+
+def bzl_test(name, src, deps):
+    """Provides build-time assurances that `bzl_library` declarations exist and are referenced properly.
+
+    This macro relies upon Stardoc's ability to traverse `bzl_library`
+    dependencies. If a Starlark dependency is loaded, but not specified as a
+    dependency, the Stardoc utility will fail with a reasonably helpful error
+    message. Interestingly, the Stardoc utility does not apply the same rigor
+    to files that are directly specifed to it.
+
+    Another point worth metioning is that the `src` file cannot be generated
+    for this macro to work. If one tries to use a generated file, the `input`
+    for the `stardoc` rule will resolve to the label for the generated file
+    which will cause the Stardoc utility to not find the file. Specifying the
+    input in different ways (i.e. filename vs target name) did not seem to
+    affect this behavior.
+
+    Args:
+        name: The name of the build target.
+        src: A non-generated Starlark file that loads the `bzl_library` that is
+             being checked.
+        deps: A `list` of deps for the Starlark file.
+
+    Returns:
+    """
+    macro_lib_name = name + "_macro_lib"
+    bzl_library(
+        name = macro_lib_name,
+        srcs = [src],
+        deps = deps,
+    )
+
+    stardoc(
+        name = name,
+        out = macro_lib_name + ".md_",
+        input = src,
+        deps = [macro_lib_name],
+    )
diff --git a/test/features_bzl_macro.bzl b/test/features_bzl_macro.bzl
new file mode 100644
index 0000000..9d2e3ce
--- /dev/null
+++ b/test/features_bzl_macro.bzl
@@ -0,0 +1,16 @@
+"""Macro used with bzl_test
+
+For more information, please see `bzl_test.bzl`.
+"""
+
+load("//:features.bzl", "bazel_features")
+
+def macro_with_doc(name):
+    """This macro does nothing.
+
+    Args:
+        name: A `string` value.
+    """
+    if name == None:
+        return None
+    return bazel_features