Commit aa51a5c557

Krzysztof Wolicki <der.teufel.mail@gmail.com>
2023-04-26 23:05:02
autodoc: Gather and display decltests
1 parent fd6200e
Changed files (2)
lib
docs
src
lib/docs/main.js
@@ -563,12 +563,14 @@ const NAV_MODES = {
 
     let currentType = getType(mod.main);
     curNav.declObjs = [currentType];
+    let lastDecl = mod.main;
     for (let i = 0; i < curNav.declNames.length; i += 1) {
       let childDecl = findSubDecl(currentType, curNav.declNames[i]);
       window.last_decl = childDecl;
       if (childDecl == null) {
         return render404();
       }
+      lastDecl = childDecl;
 
       let childDeclValue = resolveValue(childDecl.value).expr;
       if ("type" in childDeclValue) {
@@ -593,9 +595,7 @@ const NAV_MODES = {
     let lastIsType = isType(last);
     let lastIsContainerType = isContainerType(last);
 
-    if (lastIsDecl) {
-      renderDocTest(last);
-    }
+    renderDocTest(lastDecl);
 
     if (lastIsContainerType) {
       return renderContainer(last);
@@ -642,7 +642,25 @@ const NAV_MODES = {
     if (!decl.decltest) return;
     const astNode = getAstNode(decl.decltest);
     domSectDocTests.classList.remove("hidden");
-    domDocTestsCode.innerHTML = astNode.code;
+    domDocTestsCode.innerHTML = renderZigSource(astNode.code);
+  }
+
+  function renderZigSource(code) {
+    let lines = code.split("\n");
+    let result = "";
+    let indent_level = 0;
+    for(let i = 0; i < lines.length; i += 1) {
+      let line = lines[i].trim();
+      if(line[0] == "}") indent_level -= 1;
+      for(let j = 0; j < indent_level; j += 1) {
+        result += "    ";
+      }
+      if (line.startsWith("\\\\")) result += "    "
+      result += line;
+      result += "\n";
+      if(line[line.length - 1] == "{") indent_level += 1;
+    }
+    return result;
   }
 
   function renderUnknownDecl(decl) {
src/Autodoc.zig
@@ -3104,7 +3104,9 @@ fn analyzeAllDecls(
     while (it.next()) |d| {
         const decl_name_index = file.zir.extra[d.sub_index + 5];
         switch (decl_name_index) {
-            0, 1, 2 => continue, // skip over usingnamespace decls
+            0, 1 => continue, // skip over usingnamespace decls
+            2 => continue, // skip decltests
+
             else => if (file.zir.string_bytes[decl_name_index] == 0) {
                 continue;
             },
@@ -3120,6 +3122,24 @@ fn analyzeAllDecls(
         );
     }
 
+    // Fourth loop to analyze decltests
+    it = original_it;
+    while (it.next()) |d| {
+        const decl_name_index = file.zir.extra[d.sub_index + 5];
+        switch (decl_name_index) {
+            0, 1 => continue, // skip over usingnamespace decls
+            2 => {},
+            else => continue, // skip tests and normal decls
+        }
+
+        try self.analyzeDecltest(
+            file,
+            scope,
+            parent_src,
+            d,
+        );
+    }
+
     return it.extra_index;
 }
 
@@ -3327,6 +3347,56 @@ fn analyzeUsingnamespaceDecl(
     }
 }
 
+fn analyzeDecltest(
+    self: *Autodoc,
+    file: *File,
+    scope: *Scope,
+    parent_src: SrcLocInfo,
+    d: Zir.DeclIterator.Item,
+) AutodocErrors!void {
+    const data = file.zir.instructions.items(.data);
+
+    const value_index = file.zir.extra[d.sub_index + 6];
+    const decl_name_index = file.zir.extra[d.sub_index + 7];
+
+    // This is known to work because decl values are always block_inlines
+    const value_pl_node = data[value_index].pl_node;
+    const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src);
+
+    const func_index = getBlockInlineBreak(file.zir, value_index).?;
+    const pl_node = data[Zir.refToIndex(func_index).?].pl_node;
+    const fn_src = try self.srcLocInfo(file, pl_node.src_node, decl_src);
+    const tree = try file.getTree(self.comp_module.gpa);
+    const test_source_code = tree.getNodeSource(fn_src.src_node);
+
+    const decl_name: ?[]const u8 = if (decl_name_index != 0)
+        file.zir.nullTerminatedString(decl_name_index)
+    else
+        null;
+
+    // astnode
+    const ast_node_index = idx: {
+        const idx = self.ast_nodes.items.len;
+        try self.ast_nodes.append(self.arena, .{
+            .file = self.files.getIndex(file).?,
+            .line = decl_src.line,
+            .col = 0,
+            .name = decl_name,
+            .code = test_source_code,
+        });
+        break :idx idx;
+    };
+
+    const decl_status = scope.resolveDeclName(decl_name_index, file, 0);
+
+    switch (decl_status.*) {
+        .Analyzed => |idx| {
+            self.decls.items[idx].decltest = ast_node_index;
+        },
+        else => unreachable, // we assume analyzeAllDecls analyzed other decls by this point
+    }
+}
+
 /// An unresolved path has a non-string WalkResult at its beginnig, while every
 /// other element is a string WalkResult. Resolving means iteratively map each
 /// string to a Decl / Type / Call / etc.