Support Repository Rules in Stardoc

Make the fake implementation of repository_rule register the rule
instead of blindly returning the implementation function. In this
way, the documentation generated for repository rules contains the
correct arguments.

Fixes https://github.com/bazelbuild/skydoc/issues/168

Change-Id: I4b4101a9a604282051eeaadafccdc9a987b14264
PiperOrigin-RevId: 239029265
diff --git a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
index e3ef9ea..f90f566 100644
--- a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
+++ b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java
@@ -197,7 +197,8 @@
     PlatformBootstrap platformBootstrap = new PlatformBootstrap(new FakePlatformCommon());
     PyBootstrap pyBootstrap =
         new PyBootstrap(new FakePyInfoProvider(), new FakePyRuntimeInfoProvider());
-    RepositoryBootstrap repositoryBootstrap = new RepositoryBootstrap(new FakeRepositoryModule());
+    RepositoryBootstrap repositoryBootstrap =
+        new RepositoryBootstrap(new FakeRepositoryModule(Lists.newArrayList()));
     TestingBootstrap testingBootstrap =
         new TestingBootstrap(
             new FakeTestingModule(),
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
index 5fbe391..1c93b24 100644
--- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -512,7 +512,8 @@
     ProtoBootstrap protoBootstrap = new ProtoBootstrap(new FakeProtoInfoApiProvider());
     PyBootstrap pyBootstrap =
         new PyBootstrap(new FakePyInfoProvider(), new FakePyRuntimeInfoProvider());
-    RepositoryBootstrap repositoryBootstrap = new RepositoryBootstrap(new FakeRepositoryModule());
+    RepositoryBootstrap repositoryBootstrap =
+        new RepositoryBootstrap(new FakeRepositoryModule(ruleInfoList));
     TestingBootstrap testingBootstrap =
         new TestingBootstrap(
             new FakeTestingModule(),
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index 9690686..875b3ca 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -206,7 +206,7 @@
    * A comparator for {@link AttributeInfo} objects which sorts by attribute name alphabetically,
    * except that any attribute named "name" is placed first.
    */
-  private static class AttributeNameComparator implements Comparator<AttributeInfo> {
+  public static class AttributeNameComparator implements Comparator<AttributeInfo> {
 
     @Override
     public int compare(AttributeInfo o1, AttributeInfo o2) {
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/BUILD b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/BUILD
index 47b87fa..9c349e9 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/BUILD
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/BUILD
@@ -16,5 +16,9 @@
         "//src/main/java/com/google/devtools/build/lib:skylarkinterface",
         "//src/main/java/com/google/devtools/build/lib:syntax",
         "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/repository",
+        "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi",
+        "//src/main/java/com/google/devtools/build/skydoc/rendering",
+        "//third_party:guava",
+        "//third_party:jsr305",
     ],
 )
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
index 50256e0..82ec124 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
@@ -14,16 +14,36 @@
 
 package com.google.devtools.build.skydoc.fakebuildapi.repository;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryModuleApi;
 import com.google.devtools.build.lib.syntax.BaseFunction;
 import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
 import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeDescriptor;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkRuleFunctionsApi.AttributeNameComparator;
+import com.google.devtools.build.skydoc.rendering.AttributeInfo;
+import com.google.devtools.build.skydoc.rendering.AttributeInfo.Type;
+import com.google.devtools.build.skydoc.rendering.RuleInfo;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 
 /**
  * Fake implementation of {@link RepositoryModuleApi}.
  */
 public class FakeRepositoryModule implements RepositoryModuleApi {
+  private static final FakeDescriptor IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR =
+      new FakeDescriptor(Type.NAME, "A unique name for this repository.", true);
+
+  private final List<RuleInfo> ruleInfoList;
+
+  public FakeRepositoryModule(List<RuleInfo> ruleInfoList) {
+    this.ruleInfoList = ruleInfoList;
+  }
 
   @Override
   public BaseFunction repositoryRule(
@@ -33,7 +53,55 @@
       SkylarkList<String> environ,
       String doc,
       FuncallExpression ast,
-      Environment env) {
-    return implementation;
+      Environment env)
+      throws EvalException {
+    List<AttributeInfo> attrInfos;
+    ImmutableMap.Builder<String, FakeDescriptor> attrsMapBuilder = ImmutableMap.builder();
+    if (attrs != null && attrs != Runtime.NONE) {
+      SkylarkDict<?, ?> attrsDict = (SkylarkDict<?, ?>) attrs;
+      attrsMapBuilder.putAll(attrsDict.getContents(String.class, FakeDescriptor.class, "attrs"));
+    }
+
+    attrsMapBuilder.put("name", IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR);
+    attrInfos =
+        attrsMapBuilder.build().entrySet().stream()
+            .filter(entry -> !entry.getKey().startsWith("_"))
+            .map(
+                entry ->
+                    new AttributeInfo(
+                        entry.getKey(),
+                        entry.getValue().getDocString(),
+                        entry.getValue().getType(),
+                        entry.getValue().isMandatory()))
+            .collect(Collectors.toList());
+    attrInfos.sort(new AttributeNameComparator());
+
+    RepositoryRuleDefinitionIdentifier functionIdentifier =
+        new RepositoryRuleDefinitionIdentifier();
+
+    ruleInfoList.add(new RuleInfo(functionIdentifier, ast.getLocation(), doc, attrInfos));
+    return functionIdentifier;
+  }
+
+  /**
+   * A fake {@link BaseFunction} implementation which serves as an identifier for a rule definition.
+   * A skylark invocation of 'rule()' should spawn a unique instance of this class and return it.
+   * Thus, skylark code such as 'foo = rule()' will result in 'foo' being assigned to a unique
+   * identifier, which can later be matched to a registered rule() invocation saved by the fake
+   * build API implementation.
+   */
+  private static class RepositoryRuleDefinitionIdentifier extends BaseFunction {
+
+    private static int idCounter = 0;
+
+    public RepositoryRuleDefinitionIdentifier() {
+      super("RepositoryRuleDefinitionIdentifier" + idCounter++);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+      // Use exact object matching.
+      return this == other;
+    }
   }
 }
diff --git a/src/test/java/com/google/devtools/build/skydoc/BUILD b/src/test/java/com/google/devtools/build/skydoc/BUILD
index 2cf576d..bcfc620 100644
--- a/src/test/java/com/google/devtools/build/skydoc/BUILD
+++ b/src/test/java/com/google/devtools/build/skydoc/BUILD
@@ -45,6 +45,13 @@
 )
 
 skydoc_test(
+    name = "repo_rule_test",
+    golden_file = "testdata/repo_rules_test/golden.txt",
+    input_file = "testdata/repo_rules_test/input.bzl",
+    skydoc = "//src/main/java/com/google/devtools/build/skydoc",
+)
+
+skydoc_test(
     name = "unknown_name",
     golden_file = "testdata/unknown_name_test/golden.txt",
     input_file = "testdata/unknown_name_test/input.bzl",
diff --git a/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/golden.txt b/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/golden.txt
new file mode 100644
index 0000000..9bd9112
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/golden.txt
@@ -0,0 +1,39 @@
+<a name="#my_repo"></a>
+## my_repo
+
+<pre>
+my_repo(<a href="#my_repo-name">name</a>, <a href="#my_repo-useless">useless</a>)
+</pre>
+
+Minimal example of a repository rule.
+
+### Attributes
+
+<table class="params-table">
+  <colgroup>
+    <col class="col-param" />
+    <col class="col-description" />
+  </colgroup>
+  <tbody>
+    <tr id="my_repo-name">
+      <td><code>name</code></td>
+      <td>
+        <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+        <p>
+          A unique name for this repository.
+        </p>
+      </td>
+    </tr>
+    <tr id="my_repo-useless">
+      <td><code>useless</code></td>
+      <td>
+        String; optional
+        <p>
+          This argument will be ingored. You don't have to specify it, but you may.
+        </p>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+
diff --git a/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/input.bzl b/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/input.bzl
new file mode 100644
index 0000000..bac2323
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/skydoc/testdata/repo_rules_test/input.bzl
@@ -0,0 +1,13 @@
+def _repo_rule_impl(ctx):
+    ctx.file("BUILD", "")
+
+my_repo = repository_rule(
+    implementation = _repo_rule_impl,
+    doc = "Minimal example of a repository rule.",
+    attrs = {
+       "useless" : attr.string(
+         doc = "This argument will be ingored. You don't have to specify it, but you may.",
+         default = "ignoreme",
+       ),
+    },
+)