Skip to content

Commit

Permalink
Output JSDoc for functions
Browse files Browse the repository at this point in the history
  • Loading branch information
taras committed Sep 29, 2024
1 parent 6c230d9 commit 366afb5
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 78 deletions.
25 changes: 25 additions & 0 deletions www/components/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { JSXElement } from "revolution";
import type { Package } from "../hooks/use-package.tsx";

interface DescriptionProps {
pkg: Package
}

export function API({ pkg }: DescriptionProps): JSXElement {
return (
<>
{Object.keys(pkg.docs).flatMap((exportName) => {
const nodes = pkg.docs[exportName];
return nodes.map(node => {
const { MDXDoc = () => <></> } = node
return (
<>
<h3 id={node.id}>{node.name}</h3>
<MDXDoc />
</>
)
})
})}
</>
);
}
42 changes: 42 additions & 0 deletions www/components/exports.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { join } from "jsr:@std/[email protected]";
import type { JSXElement } from "revolution/jsx-runtime";

import type { DocNode } from "../hooks/use-deno-doc.tsx";
import { DEFAULT_MODULE_KEY, RenderableDocNode, type Package } from "../hooks/use-package.tsx";

export function Exports({ pkg }: { pkg: Package }): JSXElement {
return (
<>
{Object.keys(pkg.docs).map((exportName) => (
<PackageExport
packageName={pkg.packageName}
exportName={exportName}
doc={pkg.docs[exportName]}
/>
))}
</>
);
}

interface PackageExportOptions {
packageName: string;
exportName: string;
doc: Array<RenderableDocNode>;
}

function PackageExport({ packageName, exportName, doc }: PackageExportOptions) {
const exports = doc.flatMap((doc) => {
if (doc.declarationKind === "export") {
return [
<a href={`#${doc.id}`}>{doc.name}</a>, ", "];
} else {
return [];
}
}).slice(0, -1);

return (
<pre>
import &#123;{" "}<>{exports}</>{" "}&#125; from "{join(packageName, exportName)}"
</pre>
);
}
61 changes: 0 additions & 61 deletions www/components/package-exports.tsx

This file was deleted.

66 changes: 51 additions & 15 deletions www/hooks/use-package.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { call, type Operation } from "effection";
import { all, call, type Operation } from "effection";
import { join, resolve } from "jsr:@std/[email protected]";
import type { VFile } from "npm:[email protected]";
import { z } from "npm:[email protected]";
import type { JSXElement } from "revolution";

import { PrivatePackageError } from "../errors.ts";
import { type DocNode, useDenoDoc } from "./use-deno-doc.tsx";
Expand All @@ -13,19 +14,26 @@ export interface Package {
workspace: string;
packageName: string;
readme: string;
exports: string | Record<string, string>;
docs: Array<DocNode> | Record<string, Array<DocNode>>;
exports: Record<string, string>;
docs: Record<string, Array<RenderableDocNode>>;
MDXContent: () => JSX.Element;
MDXDescription: () => JSX.Element;
}

export type RenderableDocNode = DocNode & {
id: string;
MDXDoc?: () => JSXElement;
};

const DenoJson = z.object({
name: z.string(),
version: z.optional(z.string()),
exports: z.union([z.record(z.string()), z.string()]),
private: z.union([z.undefined(), z.literal(true)]),
});

export const DEFAULT_MODULE_KEY = ".";

export function* usePackage(workspace: string): Operation<Package> {
const workspacePath = resolve(
import.meta.dirname ?? "",
Expand All @@ -52,28 +60,56 @@ export function* usePackage(workspace: string): Operation<Package> {

let file: VFile = yield* useRemarkParse(readme);

let docs: Package["docs"];
if (typeof denoJson.exports === "string") {
docs = yield* useDenoDoc(
`${new URL(join(workspacePath, denoJson.exports), "file://")}`,
);
} else {
docs = {};
for (const key of Object.keys(denoJson.exports)) {
docs[key] = yield* useDenoDoc(
`${new URL(join(workspacePath, denoJson.exports[key]), "file://")}`,
);
const exports = typeof denoJson.exports === "string"
? {
[DEFAULT_MODULE_KEY]: denoJson.exports,
}
: denoJson.exports;

const entrypoints: Record<string, URL> = {};
for (const key of Object.keys(exports)) {
entrypoints[key] = new URL(join(workspacePath, exports[key]), "file://");
}

let docs: Package["docs"] = {};
for (const key of Object.keys(entrypoints)) {
const docNodes = yield* useDenoDoc(String(entrypoints[key]));
docs[key] = yield* all(docNodes.map(function* (node) {
if (node.jsDoc && node.jsDoc.doc) {
try {
const mod = yield* useMDX(node.jsDoc.doc);
return {
id: exportHash(key, node),
...node,
MDXDoc: () => mod.default({})
};
} catch (e) {
console.error(`Could not parse doc string for ${node.name} at ${node.location}`, e)
}
}
return {
id: exportHash(key, node),
...node
};
}));
}

return {
workspace: workspace.replace("./", ""),
path: workspacePath,
packageName: denoJson.name,
exports: denoJson.exports,
exports,
readme,
docs,
MDXContent: () => content,
MDXDescription: () => <>{file.data?.meta?.description}</>,
};
}

function exportHash(exportName: string, doc: DocNode): string {
if (exportName === DEFAULT_MODULE_KEY) {
return doc.name
} else {
return `${exportName}__${doc.name}`;
}
}
6 changes: 4 additions & 2 deletions www/routes/package.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { useAppHtml } from "./app.html.tsx";
import type { Package } from "../hooks/use-package.tsx";
import type { RoutePath, SitemapRoute } from "effection-www/plugins/sitemap.ts";
import { usePackages } from "../hooks/use-packages.ts";
import { PackageExports } from "../components/package-exports.tsx";
import { Exports } from "../components/exports.tsx";
import { API } from "../components/api.tsx";

export function packageRoute(): SitemapRoute<JSXElement> {
return {
Expand Down Expand Up @@ -56,7 +57,8 @@ export function packageRoute(): SitemapRoute<JSXElement> {
<p>
<pkg.MDXDescription />
</p>
<PackageExports pkg={pkg} />
<Exports pkg={pkg} />
<API pkg={pkg} />
</>
</AppHTML>
);
Expand Down

0 comments on commit 366afb5

Please sign in to comment.