docs: Make bzl files processable by stardoc.

Stardoc is picky about Args/Returns formatting, so a newline must be added
between them.

Also adds some tests to ensure they remain processable.

PiperOrigin-RevId: 516121393
diff --git a/MODULE.bazel b/MODULE.bazel
index d8969f7..72201ae 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -5,3 +5,12 @@
 )
 
 bazel_dep(name = "bazel_skylib", version = "1.3.0")
+
+# TODO(https://github.com/bazelbuild/stardoc/issues/117): stardoc doesn't yet
+# work with bzlmod enabled. This defines the repo so load() works.
+bazel_dep(
+    name = "stardoc",
+    version = "0.5.3",
+    dev_dependency = True,
+    repo_name = "io_bazel_stardoc",
+)
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
index 4a5edce..ccec4f2 100644
--- a/WORKSPACE.bazel
+++ b/WORKSPACE.bazel
@@ -12,3 +12,16 @@
         "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
     ],
 )
+
+http_archive(
+    name = "io_bazel_stardoc",
+    sha256 = "3fd8fec4ddec3c670bd810904e2e33170bedfe12f90adf943508184be458c8bb",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.3/stardoc-0.5.3.tar.gz",
+        "https://github.com/bazelbuild/stardoc/releases/download/0.5.3/stardoc-0.5.3.tar.gz",
+    ],
+)
+
+load("@io_bazel_stardoc//:setup.bzl", "stardoc_repositories")
+
+stardoc_repositories()
diff --git a/docgen/BUILD b/docgen/BUILD
new file mode 100644
index 0000000..7df0f37
--- /dev/null
+++ b/docgen/BUILD
@@ -0,0 +1,38 @@
+# Generated documentation for the docs/ directory
+
+load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+
+stardoc(
+    name = "truth",
+    out = "truth.md",
+    input = "//lib:truth.bzl",
+    tags = ["skip-bzlmod"],
+    deps = ["//lib:truth_bzl"],
+)
+
+stardoc(
+    name = "analysis_test",
+    out = "analysis_test.md",
+    input = "//lib:analysis_test.bzl",
+    tags = ["skip-bzlmod"],
+    deps = ["//lib:analysis_test_bzl"],
+)
+
+stardoc(
+    name = "util",
+    out = "util.md",
+    input = "//lib:util.bzl",
+    tags = ["skip-bzlmod"],
+    deps = ["//lib:util_bzl"],
+)
+
+build_test(
+    name = "docs_build_test",
+    tags = ["skip-bzlmod"],
+    targets = [
+        ":truth",
+        ":util",
+        ":analysis_test",
+    ],
+)
diff --git a/lib/BUILD b/lib/BUILD
index 6a13451..c320372 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -65,3 +65,14 @@
         "//tools/build_defs/python/tests/base_rules:__pkg__",
     ],
 )
+
+exports_files(
+    srcs = [
+        "analysis_test.bzl",
+        "truth.bzl",
+        "util.bzl",
+    ],
+    visibility = [
+        "//docgen:__pkg__",
+    ],
+)
diff --git a/lib/analysis_test.bzl b/lib/analysis_test.bzl
index e421297..712f092 100644
--- a/lib/analysis_test.bzl
+++ b/lib/analysis_test.bzl
@@ -167,6 +167,7 @@
           in addition to those set up by default for the test harness itself.
       collect_actions_recursively: If true, runs testing_aspect over all attributes, otherwise
           it is only applied to the target under test.
+
     Returns:
         (None)
     """
diff --git a/lib/truth.bzl b/lib/truth.bzl
index 2af336f..43bdb7f 100644
--- a/lib/truth.bzl
+++ b/lib/truth.bzl
@@ -190,6 +190,7 @@
         self: implicitly added.
         value: bool; the bool to check.
         expr: str; the starting "value of" expression to report in errors.
+
     Returns:
         A `BoolSubject` (see `_bool_subject_new`).
     """
@@ -266,6 +267,7 @@
         self: implicitly added.
         value: int; the value to check against.
         expr: str; the starting "value of" expression to report in errors.
+
     Returns:
         A struct representing an "IntSubject" (see `_int_subject_new`).
     """
@@ -277,6 +279,7 @@
     Args:
         self: implicitly added.
         value: str; the value to check against.
+
     Returns:
         A struct representing an "StrSubject" (see `_str_subject_new`).
     """
@@ -320,6 +323,7 @@
         **details: str; Each named arg is added to the metadata details
             with the provided string, which is printed as part of displaying
             any failures.
+
     Returns:
         `Expect` object with separate metadata derived from the original self.
     """
@@ -408,6 +412,7 @@
     include the context of the parent creator.
 
     Example usage:
+
         def _foo_subject_action_named(self, name):
             meta = self.meta.derive("action_named({})".format(name),
                                     "action: {}".format(...))
@@ -578,6 +583,7 @@
     Method: ActionSubject.new
 
     Example usage:
+
         expect(env).that_action(action).not_contains_arg("foo")
 
     Args:
@@ -898,6 +904,7 @@
     Args:
         value: bool; the value to assert against.
         meta: `Expectmeta` struct; the metadata about the call chain.
+
     Returns:
         A "BoolSubject" struct.
     """
@@ -958,6 +965,7 @@
         container_name: str; conceptual name of the container.
         sortable: bool; True if output should be sorted for display, false if not.
         element_plural_name: str; the plural word for the values in the container.
+
     Returns:
         A struct representing a "CollectionSubject".
     """
@@ -1235,6 +1243,7 @@
         meta: ExpectMeta struct.
         container_name: str; conceptual name of the container.
         element_plural_name: str; the plural word for the values in the container.
+
     Returns:
         A struct representing a DepsetFile object.
     """
@@ -1652,6 +1661,7 @@
     Args:
         value: optional int; the value to perform asserts against; may be None.
         meta: ExpectMeta struct; the meta data about the call chain.
+
     Returns:
         A struct representing an "IntSubject".
     """
@@ -2349,6 +2359,7 @@
 
     Args:
         self: implicitly added.
+
     Returns:
         A RunfilesSubject struct (see `_runfiles_subject_new`)
     """
@@ -2389,6 +2400,7 @@
 
     Args:
         self: implicitly added.
+
     Returns:
         A RunfilesSubject struct (see `_runfiles_subject_new`)
     """
@@ -2402,6 +2414,7 @@
 
     Args:
         self: implicitly added.
+
     Returns:
         A DepsetFileSubject struct (see `_depset_file_subject_new`).
     """
@@ -2415,6 +2428,7 @@
 
     Args:
         self: implicitly added.
+
     Returns:
         a FileSubject struct (see `_file_subject_new).
     """
@@ -2428,6 +2442,7 @@
 
     Args:
         self: implicitly added
+
     Returns:
         A CollectionSubject (of strs).
     """
@@ -2531,6 +2546,7 @@
         short_path: str; the output's short_path to match. The value is
             formatted using `format_path`, so its template keywords can be
             directly passed.
+
     Returns:
         `ActionSubject` for the matching action. If no action is found, or
         more than one action matches, then an error is raised.
@@ -3623,6 +3639,7 @@
         desc: str; a human-friendly string describing what is matched.
         func: callable; accepts 1 positional arg (the value to match) and
             returns bool (True if it matched, False if not).
+
     Returns:
         a "Matcher" struct (see above).
     """
@@ -3647,6 +3664,7 @@
 
     Args:
         substr: str; the substring to match.
+
     Returns:
         `Matcher` (see `_match_custom()`).
     """
@@ -3679,6 +3697,7 @@
 
     Args:
         values: The collection that the value must be within.
+
     Returns:
         `Matcher` (see `_match_custom()`).
     """