Render documentation for provider `init` callbacks (#224)

By default, we want the following behavior:

* Custom init callback specified
  * The set of parameters for the init callback equals the set of
    fields for the provider; and the docs for the init callback's
    parameters are either empty or equal to corresponding field docs
    * Some init parameters have a default value:
      -> Render a single "Fields" table with 3 columns (name, doc,
         default value)
    * ... otherwise
      -> Render a single "Fields" table with 2 columns
  * ... otherwise
    -> Render two tables - "Constructor parameters" and "Fields" - with
       the links from the summary blurb (interfixed with "_init")
       leading to the parameters table (not the fields table)
* ... otherwise
  -> Trivial case - single "Fields" table (as before).

Fixes #182
diff --git a/src/main/java/com/google/devtools/build/stardoc/renderer/RendererMain.java b/src/main/java/com/google/devtools/build/stardoc/renderer/RendererMain.java
index 6503e6f..fc8fca0 100644
--- a/src/main/java/com/google/devtools/build/stardoc/renderer/RendererMain.java
+++ b/src/main/java/com/google/devtools/build/stardoc/renderer/RendererMain.java
@@ -46,6 +46,7 @@
  */
 public final class RendererMain {
 
+  @SuppressWarnings("ProtoParseWithRegistry") // See https://github.com/bazelbuild/stardoc/pull/221
   public static void main(String[] args) throws IOException {
 
     RendererOptions rendererOptions = new RendererOptions();
diff --git a/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownRenderer.java b/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownRenderer.java
index 991378c..c2eb876 100644
--- a/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownRenderer.java
+++ b/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownRenderer.java
@@ -14,12 +14,17 @@
 
 package com.google.devtools.build.stardoc.rendering;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.AspectInfo;
+import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.FunctionParamInfo;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ModuleExtensionInfo;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ModuleInfo;
+import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ProviderFieldInfo;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.ProviderInfo;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.RepositoryRuleInfo;
 import com.google.devtools.build.lib.starlarkdocextract.StardocOutputProtos.RuleInfo;
@@ -157,8 +162,65 @@
   /**
    * Returns a markdown rendering of provider documentation for the given provider information
    * object with the given name.
+   *
+   * <p>For evaluating the provider template, populates the the following constants:
+   *
+   * <ul>
+   *   <li>util - a {@link MarkdownUtil} object
+   *   <li>providerName - the provider's name
+   *   <li>providerInfo - the {@link ProviderInfo} proto
+   *   <li>initParamsWithInferredDocs - the list of the init callback's {@link FunctionParamInfo}
+   *       protos, with any undocumented parameters inheriting the doc string of the provider's
+   *       field with the same name; or an empty list of the provider doesn't have an init callback
+   *   <li>initParamNamesEqualFieldNames - true iff the provider has an init callback and the set of
+   *       names of the init callback's parameters equals the set of names of the provider's fields
+   *   <li>initParamsHaveDefaultValues - true iff the provider has an init callback and at least one
+   *       of the init callback's parameters has a default value specified
+   *   <li>initParamsHaveDistinctDocs - true iff the provider has an init callback and at least one
+   *       of the init callback's parameters has a docstring which is non-empty and not equal to the
+   *       corresponding field's docstring.
+   * </ul>
    */
   public String render(ProviderInfo providerInfo) throws IOException {
+    ImmutableMap.Builder<String, String> fieldDocsBuilder = ImmutableMap.builder();
+    for (ProviderFieldInfo fieldInfo : providerInfo.getFieldInfoList()) {
+      fieldDocsBuilder.put(fieldInfo.getName(), fieldInfo.getDocString());
+    }
+    ImmutableMap<String, String> fieldDocs = fieldDocsBuilder.buildOrThrow();
+
+    ImmutableList<FunctionParamInfo> initParamsWithInferredDocs;
+    if (providerInfo.hasInit()) {
+      initParamsWithInferredDocs =
+          providerInfo.getInit().getParameterList().stream()
+              .map(param -> withInferredDoc(param, fieldDocs))
+              .collect(toImmutableList());
+    } else {
+      initParamsWithInferredDocs = ImmutableList.of();
+    }
+    boolean initParamNamesEqualFieldNames =
+        providerInfo.hasInit()
+            && providerInfo.getInit().getParameterList().stream()
+                .map(FunctionParamInfo::getName)
+                .collect(toImmutableSet())
+                .equals(
+                    providerInfo.getFieldInfoList().stream()
+                        .map(ProviderFieldInfo::getName)
+                        .collect(toImmutableSet()));
+    boolean initParamsHaveDefaultValues =
+        providerInfo.hasInit()
+            && providerInfo.getInit().getParameterList().stream()
+                .filter(param -> !param.getDefaultValue().isEmpty())
+                .findFirst()
+                .isPresent();
+    boolean initParamsHaveDistinctDocs =
+        providerInfo.hasInit()
+            && providerInfo.getInit().getParameterList().stream()
+                .filter(
+                    param ->
+                        !param.getDocString().isEmpty()
+                            && !param.getDocString().equals(fieldDocs.get(param.getName())))
+                .findFirst()
+                .isPresent();
     ImmutableMap<String, Object> vars =
         ImmutableMap.of(
             "util",
@@ -166,7 +228,15 @@
             "providerName",
             providerInfo.getProviderName(),
             "providerInfo",
-            providerInfo);
+            providerInfo,
+            "initParamsWithInferredDocs",
+            initParamsWithInferredDocs,
+            "initParamNamesEqualFieldNames",
+            initParamNamesEqualFieldNames,
+            "initParamsHaveDefaultValues",
+            initParamsHaveDefaultValues,
+            "initParamsHaveDistinctDocs",
+            initParamsHaveDistinctDocs);
     Reader reader = readerFromPath(providerTemplateFilename);
     try {
       return Template.parseFrom(reader).evaluate(vars);
@@ -175,6 +245,18 @@
     }
   }
 
+  private static FunctionParamInfo withInferredDoc(
+      FunctionParamInfo paramInfo, ImmutableMap<String, String> fallbackDocs) {
+    if (paramInfo.getDocString().isEmpty() && fallbackDocs.containsKey(paramInfo.getName())) {
+      return paramInfo.toBuilder()
+          .clearDocString()
+          .setDocString(fallbackDocs.get(paramInfo.getName()))
+          .build();
+    } else {
+      return paramInfo;
+    }
+  }
+
   /**
    * Returns a markdown rendering of a user-defined function's documentation for the function info
    * object.
diff --git a/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownUtil.java b/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownUtil.java
index acde9ae..1909964 100644
--- a/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownUtil.java
+++ b/src/main/java/com/google/devtools/build/stardoc/rendering/MarkdownUtil.java
@@ -37,6 +37,7 @@
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -223,15 +224,36 @@
   /**
    * Return a string representing the summary for the given provider with the given name.
    *
-   * <p>For example: 'MyInfo(foo, bar)'. The summary will contain hyperlinks for each field.
+   * <p>For example: 'MyInfo(foo, bar)'.
+   *
+   * <p>If the provider has an init callback, the summary will contain hyperlinks for each of the
+   * init callback's parameters; if the provider doesn't have an init callback, the summary will
+   * contain hyperlinks for each field.
    */
   @SuppressWarnings("unused") // Used by markdown template.
   public String providerSummary(String providerName, ProviderInfo providerInfo) {
-    ImmutableList<String> fieldNames =
-        providerInfo.getFieldInfoList().stream()
-            .map(field -> field.getName())
-            .collect(toImmutableList());
-    return summary(providerName, fieldNames);
+    return providerSummaryImpl(
+        providerName, providerInfo, param -> String.format("%s-%s", providerName, param));
+  }
+
+  /** Like {@link providerSummary}, but using "$providerName-_init" in HTML anchors. */
+  @SuppressWarnings("unused") // Used by markdown template.
+  public String providerSummaryWithInitAnchor(String providerName, ProviderInfo providerInfo) {
+    return providerSummaryImpl(
+        providerName, providerInfo, param -> String.format("%s-_init-%s", providerName, param));
+  }
+
+  private String providerSummaryImpl(
+      String providerName, ProviderInfo providerInfo, UnaryOperator<String> paramAnchorNamer) {
+    ImmutableList<String> paramNames =
+        providerInfo.hasInit()
+            ? providerInfo.getInit().getParameterList().stream()
+                .map(FunctionParamInfo::getName)
+                .collect(toImmutableList())
+            : providerInfo.getFieldInfoList().stream()
+                .map(field -> field.getName())
+                .collect(toImmutableList());
+    return summary(providerName, paramNames, paramAnchorNamer);
   }
 
   /**
@@ -310,14 +332,24 @@
     return summary(funcInfo.getFunctionName(), paramNames);
   }
 
-  private static String summary(String functionName, ImmutableList<String> paramNames) {
+  /**
+   * Returns a string representing the summary for a function or other callable.
+   *
+   * @param paramAnchorNamer translates a paremeter's name into the name of its HTML anchor
+   */
+  private static String summary(
+      String functionName,
+      ImmutableList<String> paramNames,
+      UnaryOperator<String> paramAnchorNamer) {
     ImmutableList<ImmutableList<String>> paramLines =
         wrap(functionName, paramNames, MAX_LINE_LENGTH);
     List<String> paramLinksLines = new ArrayList<>();
     for (List<String> params : paramLines) {
       String paramLinksLine =
           params.stream()
-              .map(param -> String.format("<a href=\"#%s-%s\">%s</a>", functionName, param, param))
+              .map(
+                  param ->
+                      String.format("<a href=\"#%s\">%s</a>", paramAnchorNamer.apply(param), param))
               .collect(joining(", "));
       paramLinksLines.add(paramLinksLine);
     }
@@ -326,6 +358,10 @@
     return String.format("%s(%s)", functionName, paramList);
   }
 
+  private static String summary(String functionName, ImmutableList<String> paramNames) {
+    return summary(functionName, paramNames, param -> String.format("%s-%s", functionName, param));
+  }
+
   /**
    * Wraps the given function parameter names to be able to construct a function summary that stays
    * within the provided line length limit.
diff --git a/stardoc/templates/html_tables/provider.vm b/stardoc/templates/html_tables/provider.vm
index b1820d5..a2919b0 100644
--- a/stardoc/templates/html_tables/provider.vm
+++ b/stardoc/templates/html_tables/provider.vm
@@ -1,12 +1,62 @@
 <a id="${providerName}"></a>
+#if ($providerInfo.hasInit() && $initParamNamesEqualFieldNames && !$initParamsHaveDistinctDocs && !$initParamsWithInferredDocs.isEmpty())
+#set ($mergeParamsAndFields = true)
+#else
+#set ($mergeParamsAndFields = false)
+#end
 
 #[[##]]# ${providerName}
 
 <pre>
+#if ($providerInfo.hasInit() && !$mergeParamsAndFields)
+${util.providerSummaryWithInitAnchor($providerName, $providerInfo)}
+#else
 ${util.providerSummary($providerName, $providerInfo)}
+#end
 </pre>
+#if (!$providerInfo.docString.isEmpty())
 
-${util.htmlEscape($providerInfo.docString)}
+${providerInfo.docString}
+#end
+#if ($providerInfo.hasInit() && !$providerInfo.init.deprecated.docString.isEmpty())
+
+#[[###]]# Deprecated
+
+${providerInfo.init.deprecated.docString}
+#end
+#if ($providerInfo.hasInit() && !$providerInfo.init.parameterList.isEmpty() && !$mergeParamsAndFields)
+
+#[[###]]# Constructor parameters
+
+<table class="params-table">
+<colgroup>
+<col class="col-param" />
+<col class="col-description" />
+</colgroup>
+<tbody>
+#foreach ($param in $initParamsWithInferredDocs)
+<tr id="${providerName}-_init-${param.name}">
+<td><code>${param.name}</code></td>
+<td>
+
+${util.mandatoryString($param)}.
+#if(!$param.getDefaultValue().isEmpty())
+default is <code>$param.getDefaultValue()</code>
+#end
+
+#if (!$param.docString.isEmpty())
+<p>
+
+${param.docString.trim()}
+
+</p>
+#end
+</td>
+</tr>
+#end
+</tbody>
+</table>
+#end
 
 #if (!$providerInfo.fieldInfoList.isEmpty())
 #[[###]]# Fields
@@ -17,6 +67,28 @@
 <col class="col-description" />
 </colgroup>
 <tbody>
+#if ($mergeParamsAndFields)
+#foreach ($param in $initParamsWithInferredDocs)
+<tr id="${providerName}-_init-${param.name}">
+<td><code>${param.name}</code></td>
+<td>
+
+${util.mandatoryString($param)}.
+#if(!$param.getDefaultValue().isEmpty())
+default is <code>$param.getDefaultValue()</code>
+#end
+
+#if (!$param.docString.isEmpty())
+<p>
+
+${param.docString.trim()}
+
+</p>
+#end
+</td>
+</tr>
+#end
+#else
 #foreach ($field in $providerInfo.fieldInfoList)
 <tr id="${providerName}-${field.name}">
 <td><code>${field.name}</code></td>
@@ -29,6 +101,7 @@
 </td>
 </tr>
 #end
+#end
 </tbody>
 </table>
 #end
diff --git a/stardoc/templates/markdown_tables/provider.vm b/stardoc/templates/markdown_tables/provider.vm
index 0a866db..f623e0d 100644
--- a/stardoc/templates/markdown_tables/provider.vm
+++ b/stardoc/templates/markdown_tables/provider.vm
@@ -1,20 +1,80 @@
 <a id="${providerName}"></a>
+#if ($providerInfo.hasInit() && $initParamNamesEqualFieldNames && !$initParamsHaveDistinctDocs && !$initParamsWithInferredDocs.isEmpty())
+#set ($mergeParamsAndFields = true)
+#else
+#set ($mergeParamsAndFields = false)
+#end
 
 #[[##]]# ${providerName}
 
 <pre>
+#if ($providerInfo.hasInit() && !$mergeParamsAndFields)
+${util.providerSummaryWithInitAnchor($providerName, $providerInfo)}
+#else
 ${util.providerSummary($providerName, $providerInfo)}
+#end
 </pre>
+#if (!$providerInfo.docString.isEmpty())
 
 ${providerInfo.docString}
+#end
+#if ($providerInfo.hasInit() && !$providerInfo.init.deprecated.docString.isEmpty())
+
+**DEPRECATED**
+
+${providerInfo.init.deprecated.docString}
+#end
+#if ($providerInfo.hasInit() && !$providerInfo.init.parameterList.isEmpty() && !$mergeParamsAndFields)
+
+**CONSTRUCTOR PARAMETERS**
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+#foreach ($param in $initParamsWithInferredDocs)
+| <a id="${providerName}-_init-${param.name}"></a>$param.name | ##
+#if (!$param.docString.isEmpty())
+${util.markdownCellFormat($param.docString)} ##
+#else
+<p align="center">-</p> ##
+#end
+| ##
+#if (!$param.getDefaultValue().isEmpty())
+${util.markdownCodeSpan($param.defaultValue)} ##
+#else
+none ##
+#end
+|
+#end
+#end
+#if (!$providerInfo.fieldInfoList.isEmpty())
 
 **FIELDS**
 
-#if (!$providerInfo.fieldInfoList.isEmpty())
-
+#if ($mergeParamsAndFields)
+| Name  | Description #if ($initParamsHaveDefaultValues)| Default Value #end|
+| :------------- | :------------- #if ($initParamsHaveDefaultValues)| :------------- #end|
+#foreach ($param in $initParamsWithInferredDocs)
+| <a id="${providerName}-${param.name}"></a>$param.name | ##
+#if (!$param.docString.isEmpty())
+${util.markdownCellFormat($param.docString)} ##
+#else
+<p align="center"> - </p> ##
+#end
+#if($initParamsHaveDefaultValues)
+| ##
+#if (!$param.getDefaultValue().isEmpty())
+${util.markdownCodeSpan($param.defaultValue)} ##
+#else
+none ##
+#end
+#end
+|
+#end
+#else
 | Name  | Description |
 | :------------- | :------------- |
 #foreach ($field in $providerInfo.fieldInfoList)
 | <a id="${providerName}-${field.name}"></a>$field.name | #if(!$field.docString.isEmpty()) ${util.markdownCellFormat($field.docString)} #else - #end   |
 #end
 #end
+#end
diff --git a/test/testdata/angle_bracket_test/golden.md b/test/testdata/angle_bracket_test/golden.md
index 06496b2..3e26bea 100644
--- a/test/testdata/angle_bracket_test/golden.md
+++ b/test/testdata/angle_bracket_test/golden.md
@@ -44,7 +44,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="bracketuse-foo"></a>foo |  A string representing \<foo>    |
diff --git a/test/testdata/misc_apis_test/golden.md b/test/testdata/misc_apis_test/golden.md
index 956bf70..4368d43 100644
--- a/test/testdata/misc_apis_test/golden.md
+++ b/test/testdata/misc_apis_test/golden.md
@@ -33,11 +33,8 @@
 MyInfo(<a href="#MyInfo-foo">foo</a>, <a href="#MyInfo-bar">bar</a>)
 </pre>
 
-
-
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyInfo-foo"></a>foo |  Something foo-related.    |
diff --git a/test/testdata/provider_basic_test/golden.md b/test/testdata/provider_basic_test/golden.md
index 7029581..ec94c4a 100644
--- a/test/testdata/provider_basic_test/golden.md
+++ b/test/testdata/provider_basic_test/golden.md
@@ -2,6 +2,128 @@
 
 
 
+<a id="MyCustomInitInfo"></a>
+
+## MyCustomInitInfo
+
+<pre>
+MyCustomInitInfo(<a href="#MyCustomInitInfo-foo">foo</a>, <a href="#MyCustomInitInfo-bar">bar</a>)
+</pre>
+
+A provider with a custom constructor.
+
+Since the custom constructor parameters match the provider's fields,
+we don't need to render a separate table of constructor parameters.
+
+**FIELDS**
+
+| Name  | Description |
+| :------------- | :------------- |
+| <a id="MyCustomInitInfo-foo"></a>foo | Foo data |
+| <a id="MyCustomInitInfo-bar"></a>bar | Bar data. |
+
+
+<a id="MyCustomInitWithDefaultParamValueInfo"></a>
+
+## MyCustomInitWithDefaultParamValueInfo
+
+<pre>
+MyCustomInitWithDefaultParamValueInfo(<a href="#MyCustomInitWithDefaultParamValueInfo-foo">foo</a>, <a href="#MyCustomInitWithDefaultParamValueInfo-bar">bar</a>)
+</pre>
+
+A provider with a custom constructor with a parameter with a default value.
+
+Since the custom constructor parameters match the provider's fields,
+we don't need to render a separate table of constructor parameters - but
+we do need to render the default value.
+
+**FIELDS**
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="MyCustomInitWithDefaultParamValueInfo-foo"></a>foo | Foo data | none |
+| <a id="MyCustomInitWithDefaultParamValueInfo-bar"></a>bar | Bar data. | `42` |
+
+
+<a id="MyCustomInitWithDocumentedParamInfo"></a>
+
+## MyCustomInitWithDocumentedParamInfo
+
+<pre>
+MyCustomInitWithDocumentedParamInfo(<a href="#MyCustomInitWithDocumentedParamInfo-_init-foo">foo</a>, <a href="#MyCustomInitWithDocumentedParamInfo-_init-bar">bar</a>)
+</pre>
+
+A provider with a custom constructor with documented constructor parameters.
+
+Docs for constructor parameters differ from docs for fields, so we need to render
+constructor parameters as a separate table.
+
+**CONSTRUCTOR PARAMETERS**
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="MyCustomInitWithDocumentedParamInfo-_init-foo"></a>foo | Foo data; must be non-negative | none |
+| <a id="MyCustomInitWithDocumentedParamInfo-_init-bar"></a>bar | Bar data. Note that we didn't document `bar` parameter for the init callback - we want this docstring to be propagated to the constructor param table. | `42` |
+
+**FIELDS**
+
+| Name  | Description |
+| :------------- | :------------- |
+| <a id="MyCustomInitWithDocumentedParamInfo-foo"></a>foo |  Foo data    |
+| <a id="MyCustomInitWithDocumentedParamInfo-bar"></a>bar |  Bar data. Note that we didn't document `bar` parameter for the init callback - we want this docstring to be propagated to the constructor param table.    |
+
+
+<a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo"></a>
+
+## MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo
+
+<pre>
+MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo(<a href="#MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-_init-foo">foo</a>, <a href="#MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-_init-bar">bar</a>)
+</pre>
+
+A provider with a custom constructor whose set of constructor parameters does not equal the provider's set of fields.
+
+We have no choice - we need to render constructor parameters as a separate table.
+
+**CONSTRUCTOR PARAMETERS**
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-_init-foo"></a>foo | Foo data | none |
+| <a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-_init-bar"></a>bar | Bar data. | none |
+
+**FIELDS**
+
+| Name  | Description |
+| :------------- | :------------- |
+| <a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-foo"></a>foo |  Foo data    |
+| <a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-bar"></a>bar |  Bar data.    |
+| <a id="MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo-validated"></a>validated |  True, hopefully    |
+
+
+<a id="MyDeprecatedInfo"></a>
+
+## MyDeprecatedInfo
+
+<pre>
+MyDeprecatedInfo()
+</pre>
+
+You can read this info.
+
+But should you really construct it?
+
+**DEPRECATED**
+
+Do not construct!
+
+**FIELDS**
+
+| Name  | Description |
+| :------------- | :------------- |
+| <a id="MyDeprecatedInfo-foo"></a>foo |  Foo    |
+
+
 <a id="MyFooInfo"></a>
 
 ## MyFooInfo
@@ -14,7 +136,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyFooInfo-bar"></a>bar |  -    |
@@ -30,11 +151,6 @@
 </pre>
 
 
-
-**FIELDS**
-
-
-
 <a id="MyVeryDocumentedInfo"></a>
 
 ## MyVeryDocumentedInfo
@@ -49,7 +165,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyVeryDocumentedInfo-favorite_food"></a>favorite_food |  A string representing my favorite food<br><br>Expected to be delicious.    |
diff --git a/test/testdata/provider_basic_test/input.bzl b/test/testdata/provider_basic_test/input.bzl
index cc3068d..d6b7f8b 100644
--- a/test/testdata/provider_basic_test/input.bzl
+++ b/test/testdata/provider_basic_test/input.bzl
@@ -24,6 +24,132 @@
     },
 )
 
+def _init_my_custom_init_info(foo, bar):
+    """
+    Validate stuff.
+
+    Technical details; the user probably doesn't want to see this part.
+    """
+    if foo < 0:
+        fail("foo must be non-negative")
+
+    return {"foo": foo, "bar": bar, "validated": True}
+
+MyCustomInitInfo, _new_my_custom_init_info = provider(
+    doc = """
+    A provider with a custom constructor.
+
+    Since the custom constructor parameters match the provider's fields,
+    we don't need to render a separate table of constructor parameters.
+    """,
+    init = _init_my_custom_init_info,
+    fields = {
+        "foo": "Foo data",
+        "bar": "Bar data.",
+    },
+)
+
+def _init_my_custom_init_with_default_param_value_info(foo, bar = 42):
+    """
+    Validate stuff.
+
+    Technical details; the user probably doesn't want to see this part.
+    """
+    if foo < 0:
+        fail("foo must be non-negative")
+
+    return {"foo": foo, "bar": bar, "validated": True}
+
+MyCustomInitWithDefaultParamValueInfo, _new_my_custom_init_with_default_param_value_info = provider(
+    doc = """
+    A provider with a custom constructor with a parameter with a default value.
+
+    Since the custom constructor parameters match the provider's fields,
+    we don't need to render a separate table of constructor parameters - but
+    we do need to render the default value.
+    """,
+    init = _init_my_custom_init_with_default_param_value_info,
+    fields = {
+        "foo": "Foo data",
+        "bar": "Bar data.",
+    },
+)
+
+def _init_my_custom_init_with_mismatching_constructor_params_and_fields_info(foo, bar):
+    """
+    Validate stuff.
+
+    Technical details; the user probably doesn't want to see this part.
+    """
+    if foo < 0:
+        fail("foo must be non-negative")
+
+    return {"foo": foo, "bar": bar, "validated": True}
+
+MyCustomInitWithMismatchingConstructorParamsAndFieldsInfo, _new_my_custom_init_with_mismatching_constructor_params_and_fields_info = provider(
+    doc = """
+    A provider with a custom constructor whose set of constructor parameters does not equal the provider's set of fields.
+    
+    We have no choice - we need to render constructor parameters as a separate table.
+    """,
+    init = _init_my_custom_init_with_mismatching_constructor_params_and_fields_info,
+    fields = {
+        "foo": "Foo data",
+        "bar": "Bar data.",
+        "validated": "True, hopefully",
+    },
+)
+
+# buildifier: disable=function-docstring-args
+def _init_my_custom_init_with_documented_param_info(foo, bar = 42):
+    """
+    Validate stuff.
+
+    Technical details; the user probably doesn't want to see this part.
+
+    Args:
+        foo: Foo data; must be non-negative
+    """
+    if foo < 0:
+        fail("foo must be non-negative")
+
+    return {"foo": foo, "bar": bar}
+
+MyCustomInitWithDocumentedParamInfo, _new_my_custom_init_with_documented_param_info = provider(
+    doc = """
+    A provider with a custom constructor with documented constructor parameters.
+    
+    Docs for constructor parameters differ from docs for fields, so we need to render
+    constructor parameters as a separate table.
+    """,
+    init = _init_my_custom_init_with_documented_param_info,
+    fields = {
+        "foo": "Foo data",
+        "bar": "Bar data. Note that we didn't document `bar` parameter for the init callback - we want this docstring to be propagated to the constructor param table.",
+    },
+)
+
+def _init_my_deprecated_info():
+    """
+    MyDeprecatedInfo constructor.
+
+    Deprecated:
+        Do not construct!
+    """
+    return {}
+
+MyDeprecatedInfo, _new_my_deprecated_info = provider(
+    doc = """
+    You can read this info.
+
+    But should you really construct it?
+    """,
+    init = _init_my_deprecated_info,
+    fields = {
+        "foo": "Foo",
+    },
+)
+
 named_providers_are_hashable = {
     MyFooInfo: "MyFooInfo is hashable",
     MyVeryDocumentedInfo: "So is MyVeryDocumentedInfo",
diff --git a/test/testdata/providers_for_attributes_test/golden.md b/test/testdata/providers_for_attributes_test/golden.md
index a427cf2..b259996 100644
--- a/test/testdata/providers_for_attributes_test/golden.md
+++ b/test/testdata/providers_for_attributes_test/golden.md
@@ -34,11 +34,8 @@
 MyProviderInfo(<a href="#MyProviderInfo-foo">foo</a>, <a href="#MyProviderInfo-bar">bar</a>)
 </pre>
 
-
-
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyProviderInfo-foo"></a>foo |  Something foo-related.    |
@@ -54,11 +51,6 @@
 </pre>
 
 
-
-**FIELDS**
-
-
-
 <a id="my_rule_impl"></a>
 
 ## my_rule_impl
diff --git a/test/testdata/pure_markdown_template_test/golden.md b/test/testdata/pure_markdown_template_test/golden.md
index 62a9327..d39dbb5 100644
--- a/test/testdata/pure_markdown_template_test/golden.md
+++ b/test/testdata/pure_markdown_template_test/golden.md
@@ -34,7 +34,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="ExampleProviderInfo-foo"></a>foo |  A string representing foo    |
diff --git a/test/testdata/table_of_contents_test/golden.md b/test/testdata/table_of_contents_test/golden.md
index 52ca9aa..a49b49c 100644
--- a/test/testdata/table_of_contents_test/golden.md
+++ b/test/testdata/table_of_contents_test/golden.md
@@ -65,7 +65,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyFooInfo-bar"></a>bar |  -    |
@@ -86,7 +85,6 @@
 
 **FIELDS**
 
-
 | Name  | Description |
 | :------------- | :------------- |
 | <a id="MyVeryDocumentedInfo-favorite_food"></a>favorite_food |  A string representing my favorite food<br><br>Expected to be delicious.    |