├── _showcase
├── static
│ ├── favicon.ico
│ └── logo.svg
├── util.ts
├── dev.ts
├── twind.config.ts
├── components
│ ├── ComponentLink.tsx
│ ├── ComponentTitle.tsx
│ ├── ShowcaseModuleDoc.tsx
│ ├── ShowcaseDocBlocks.tsx
│ └── Showcase.tsx
├── routes
│ ├── moduledoc.tsx
│ ├── _app.tsx
│ ├── docblocks.tsx
│ └── index.tsx
├── deno.json
├── main.ts
├── main.tsx
├── import_map.json
├── fresh.gen.ts
└── data.ts
├── deps_test.ts
├── deno.json
├── .github
└── workflows
│ └── ci.yml
├── doc
├── namespaces.tsx
├── variables.tsx
├── doc.ts
├── type_aliases.tsx
├── enums.tsx
├── doc_title.tsx
├── params.tsx
├── doc_block.tsx
├── symbol_kind.tsx
├── usage.tsx
├── library_doc.tsx
├── module_index.tsx
├── markdown.tsx
├── library_doc_panel.tsx
├── module_index_panel.tsx
├── doc_common.tsx
├── symbol_doc.tsx
├── module_doc.tsx
├── functions.tsx
├── interfaces.tsx
├── utils.ts
├── classes.tsx
└── types.tsx
├── LICENSE.md
├── twind.config.ts
├── deps.ts
├── doc_test.ts
├── README.md
├── services.ts
├── styles.ts
└── icons.tsx
/_showcase/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/doc_components/main/_showcase/static/favicon.ico
--------------------------------------------------------------------------------
/_showcase/util.ts:
--------------------------------------------------------------------------------
1 | export function headerify(str: string): string {
2 | return str.replaceAll(/[ -/]/g, "_").toLowerCase();
3 | }
4 |
--------------------------------------------------------------------------------
/_showcase/dev.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/
2 |
3 | import dev from "$fresh/dev.ts";
4 |
5 | await dev(import.meta.url, "./main.ts");
6 |
--------------------------------------------------------------------------------
/_showcase/twind.config.ts:
--------------------------------------------------------------------------------
1 | import { Options } from "$fresh/plugins/twind.ts";
2 | import { plugins, theme } from "@doc_components/twind.config.ts";
3 |
4 | export default {
5 | selfURL: import.meta.url,
6 | plugins,
7 | theme,
8 | darkMode: "class",
9 | } as Options;
10 |
--------------------------------------------------------------------------------
/deps_test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | ///
([^<]+)<\/code><\/pre>/m;
17 |
18 | /** Matches `{@link ...}`, `{@linkcode ...}, and `{@linkplain ...}` structures
19 | * in JSDoc */
20 | const JSDOC_LINK_RE = /\{\s*@link(code|plain)?\s+([^}]+)}/m;
21 |
22 | const MARKDOWN_OPTIONS: comrak.ComrakOptions = {
23 | extension: {
24 | autolink: true,
25 | descriptionLists: true,
26 | strikethrough: true,
27 | superscript: true,
28 | table: true,
29 | tagfilter: true,
30 | },
31 | };
32 |
33 | let lowlight: ReturnType;
34 |
35 | function syntaxHighlight(html: string): string {
36 | let match;
37 | while ((match = CODE_BLOCK_RE.exec(html))) {
38 | let [text, lang, code] = match;
39 | lang = lang.split(",")[0];
40 | let codeHTML;
41 | if (lowlight.registered(lang)) {
42 | const tree = lowlight.highlight(
43 | lang,
44 | htmlEntities.decode(code),
45 | {
46 | prefix: "code-",
47 | },
48 | );
49 | codeHTML = toHtml(tree);
50 | } else {
51 | codeHTML = code;
52 | }
53 | assert(match.index != null);
54 | html = `${html.slice(0, match.index)}${codeHTML}
${
55 | html.slice(match.index + text.length)
56 | }`;
57 | }
58 | return html;
59 | }
60 |
61 | /** Determines if the value looks like a relative or absolute path, or is
62 | * a URI with a protocol. */
63 | function isLink(link: string): boolean {
64 | return /^\.{0,2}\//.test(link) || /^[A-Za-z]+:\S/.test(link);
65 | }
66 |
67 | function parseLinks(markdown: string, url: URL, namespace?: string): string {
68 | let match;
69 | while ((match = JSDOC_LINK_RE.exec(markdown))) {
70 | const [text, modifier, value] = match;
71 | let link = value;
72 | let title;
73 | const indexOfSpace = value.indexOf(" ");
74 | const indexOfPipe = value.indexOf("|");
75 | if (indexOfPipe >= 0) {
76 | link = value.slice(0, indexOfPipe);
77 | title = value.slice(indexOfPipe + 1).trim();
78 | } else if (indexOfSpace >= 0) {
79 | link = value.slice(0, indexOfSpace);
80 | title = value.slice(indexOfSpace + 1).trim();
81 | }
82 | const href = services.lookupHref(url, namespace, link);
83 | if (href) {
84 | if (!title) {
85 | title = link;
86 | }
87 | link = href;
88 | }
89 | let replacement;
90 | if (isLink(link)) {
91 | if (title) {
92 | replacement = modifier === "code"
93 | ? `[\`${title}\`](${link})`
94 | : `[${title}](${link})`;
95 | } else {
96 | replacement = modifier === "code"
97 | ? `[\`${link}\`](${link})`
98 | : `[${link}](${link})`;
99 | }
100 | } else {
101 | replacement = modifier === "code"
102 | ? `\`${link}\`${title ? ` | ${title}` : ""}`
103 | : `${link}${title ? ` | ${title}` : ""}`;
104 | }
105 | markdown = `${markdown.slice(0, match.index)}${replacement}${
106 | markdown.slice(match.index + text.length)
107 | }`;
108 | }
109 | return markdown;
110 | }
111 |
112 | export function mdToHtml(markdown: string): string {
113 | if (!lowlight) {
114 | lowlight = createLowlight(all);
115 | }
116 | return syntaxHighlight(comrak.markdownToHTML(markdown, MARKDOWN_OPTIONS));
117 | }
118 |
119 | export interface Context {
120 | url: URL;
121 | namespace?: string;
122 | replacers?: [string, string][];
123 | typeParams?: string[];
124 | }
125 |
126 | export function Markdown(
127 | { children, summary, context }: {
128 | children: Child;
129 | summary?: boolean;
130 | context: Context;
131 | },
132 | ) {
133 | let md = take(children);
134 | if (!md) {
135 | return null;
136 | }
137 | if (context.replacers) {
138 | for (const [pattern, replacement] of context.replacers) {
139 | md = md.replaceAll(pattern, replacement);
140 | }
141 | }
142 | let mdStyle: StyleKey = "markdown";
143 | let additionalStyle = services.markdownStyle;
144 | if (summary) {
145 | mdStyle = "markdownSummary";
146 | additionalStyle = services.markdownSummaryStyle;
147 | [md] = splitMarkdownTitle(md);
148 | }
149 |
150 | return (
151 |
159 | );
160 | }
161 |
162 | export function JsDoc(
163 | { children, context }: {
164 | children: Child<{ doc?: string } | undefined>;
165 | context: Context;
166 | },
167 | ) {
168 | const jsDoc = take(children);
169 | if (!jsDoc) {
170 | return null;
171 | }
172 | return {jsDoc.doc} ;
173 | }
174 |
--------------------------------------------------------------------------------
/styles.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { apply, css, type Directive, tw } from "./deps.ts";
4 |
5 | export const comrakStyles = css({
6 | // code
7 | ":not(pre) > code": apply`font-mono text-sm py-1 px-1.5 rounded bg-gray-100`,
8 | pre:
9 | apply`font-mono text-sm p-2.5 rounded-lg text-black bg-gray-100 overflow-x-auto`,
10 |
11 | // general
12 | a: apply`link`,
13 | h1: apply`text-xl md:text-2xl lg:text-3xl`,
14 | h2: apply`text-lg md:text-xl lg:text-2xl`,
15 | h3: apply`font-bold md:text-lg md:font-normal lg:text-xl lg:font-normal`,
16 | h4: apply`font-semibold md:font-bold lg:text-lg lg:font-normal`,
17 | h5: apply`font-italic md:font-semibold lg:font-bold`,
18 | h6: apply`md:font-italic lg:font-semibold`,
19 | hr: apply`m-2 border-gray-500`,
20 | ol: apply`list-decimal lg:list-inside`,
21 | p: apply`my-1 text-left`,
22 | table: apply`table-auto`,
23 | td: apply`p-2 border border-solid border-gray-500`,
24 | th: apply`font-bold text-center`,
25 | ul: apply`lg:list-disc lg:list-inside`,
26 |
27 | // syntax highlighting
28 | ".code-comment": apply`text-gray-500`,
29 | ".code-function": apply`text-green-700`,
30 | ".code-literal": apply`text-cyan-600`,
31 | ".code-keyword, .code-operator, .code-variable.code-language":
32 | apply`text-purple-800`,
33 | ".code-number, .code-doctag": apply`text-indigo-600`,
34 | ".code-regexp": apply`text-red-700`,
35 | ".code-string": apply`text-yellow-500`,
36 | ".code-type, .code-built_in": apply`text-cyan-600 italic`,
37 | });
38 |
39 | const styles = {
40 | anchor:
41 | apply`float-left leading-none hidden group-hover:block text-gray-600 -ml-[18px] pr-[4px]`,
42 | copyButton: apply`rounded border border-gray-300 p-1.5 hover:bg-gray-100`,
43 | details: css({
44 | "& > summary": apply`list-none`,
45 | "& > summary::-webkit-details-marker": apply`hidden`,
46 | "&[open] svg": apply`rotate-90`,
47 | }),
48 | docBlockItems: apply`space-y-7`,
49 | docEntry: apply`flex justify-between`,
50 | docEntryChildren: apply`break-words flex items-center gap-2`,
51 | docItem: apply`group relative`,
52 | indent: apply`ml-4`,
53 | main: apply`space-y-7 md:col-span-3`,
54 | markdown: apply`flex flex-col space-y-4 text-justify`,
55 | markdownSummary: apply`inline text-gray-600 ${
56 | css({
57 | "p": apply`inline-block`,
58 | })
59 | }`,
60 | moduleDoc: apply`space-y-6`,
61 | moduleDocHeader: apply`flex justify-between mb-8`,
62 | moduleIndex: apply`rounded-lg w-full border border-gray-300`,
63 | moduleIndexHeader: apply`flex justify-between items-center py-3.5 pr-5`,
64 | moduleIndexHeaderTitle: apply`ml-5 font-semibold text-lg flex items-center`,
65 | moduleIndexHeaderTitleSpan: apply`ml-2 leading-none`,
66 | moduleIndexTable: apply`block lg:table w-full`,
67 | moduleIndexRow: apply`block lg:table-row odd:bg-gray-50`,
68 | moduleIndexLinkCell:
69 | apply`block lg:table-cell pl-5 pr-3 py-2.5 font-semibold`,
70 | moduleIndexLinkCellIcon: apply`inline my-1.5 mr-3`,
71 | moduleIndexDocCell:
72 | apply`block lg:table-cell lg:pl-0 lg:pt-2.5 lg:mt-0 pl-11 pr-[1.375rem] pb-2.5 -mt-2 text-gray-500`,
73 | moduleIndexPanel: apply`lg:w-72 flex-shrink-0`,
74 | moduleIndexPanelActive: apply`bg-gray-100 font-bold`,
75 | moduleIndexPanelEntry:
76 | apply`flex items-center gap-2 py-2 px-3 rounded-lg w-full leading-6 hover:text-gray-500 hover:bg-gray-50 children:last-child:truncate children:last-child:flex-shrink-1`,
77 | moduleIndexPanelModuleIndex: apply`text-gray-500 font-light`,
78 | moduleIndexPanelSymbol:
79 | apply`flex items-center justify-between gap-1 py-1.5 pl-2.5 pr-3 rounded-lg w-full leading-6 hover:text-gray-500 hover:bg-gray-50 children:first-child:flex children:first-child:items-center children:first-child:gap-2 children:first-child:min-w-0 children:first-child:children:last-child:truncate`,
80 | section: apply`text-sm leading-6 font-semibold text-gray-400 py-1`,
81 | symbolDoc: apply`space-y-12 md:col-span-3`,
82 | symbolDocHeader: apply`flex justify-between items-start`,
83 | symbolKind:
84 | apply`rounded-full w-6 h-6 inline-flex items-center justify-center font-medium text-xs leading-none flex-shrink-0 select-none`,
85 | sourceLink:
86 | apply`pl-2 break-words text-gray-600 hover:text-gray-800 hover:underline`,
87 | symbolListCellSymbol:
88 | apply`block lg:table-cell py-1 pr-3 font-bold children:space-x-2 children:min-w-[13rem] children:flex children:items-center`,
89 | symbolListCellDoc: apply`block lg:table-cell py-1 text-sm text-gray-500`,
90 | symbolListRow: apply`block lg:table-row`,
91 | symbolListTable: apply`block lg:table`,
92 | symbolKindDisplay:
93 | apply`w-11 flex-none flex children:not-first-child:-ml-[7px]`,
94 | tag:
95 | apply`inline-flex items-center gap-0.5 children:flex-none rounded-full font-medium text-sm leading-none`,
96 | } as const;
97 |
98 | export type StyleKey = keyof typeof styles;
99 |
100 | export function style(name: StyleKey): string;
101 | // deno-lint-ignore no-explicit-any
102 | export function style(name: StyleKey, raw: boolean): string | Directive;
103 | // deno-lint-ignore no-explicit-any
104 | export function style(name: StyleKey, raw: true): Directive;
105 | export function style(
106 | name: StyleKey,
107 | raw = false,
108 | // deno-lint-ignore no-explicit-any
109 | ): string | Directive {
110 | if (raw) {
111 | return styles[name];
112 | }
113 | return tw`${styles[name]}`;
114 | }
115 |
--------------------------------------------------------------------------------
/doc/library_doc_panel.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeKind, type JsDoc } from "../deps.ts";
4 | import { services } from "../services.ts";
5 | import { style } from "../styles.ts";
6 | import { type Child, take } from "./utils.ts";
7 | import * as Icons from "../icons.tsx";
8 | import { docNodeKindMap } from "./symbol_kind.tsx";
9 | import { byKindValue } from "./doc.ts";
10 | import { tagVariants } from "./doc_common.tsx";
11 | import { SymbolItem } from "./module_index_panel.tsx";
12 |
13 | export interface ProcessedSymbol {
14 | name: string;
15 | kinds: DocNodeKind[];
16 | unstable: boolean;
17 | category?: string;
18 | jsDoc?: JsDoc | null;
19 | }
20 |
21 | export function categorize(
22 | items: SymbolItem[],
23 | ): [
24 | categories: Record,
25 | uncategorized: ProcessedSymbol[],
26 | ] {
27 | const symbols: ProcessedSymbol[] = [];
28 | for (
29 | const symbolItem of items.filter((symbol) =>
30 | symbol.kind !== "import" && symbol.kind !== "moduleDoc"
31 | ).sort((a, b) =>
32 | byKindValue(a.kind, b.kind) || a.name.localeCompare(b.name)
33 | )
34 | ) {
35 | const existing = symbols.find((symbol) => symbol.name === symbolItem.name);
36 | const isUnstable = symbolItem.jsDoc?.tags?.some((tag) =>
37 | tag.kind === "tags" && tag.tags.includes("unstable")
38 | ) ?? false;
39 | if (!existing) {
40 | symbols.push({
41 | name: symbolItem.name,
42 | kinds: [symbolItem.kind],
43 | unstable: isUnstable,
44 | category: symbolItem.category?.trim(),
45 | jsDoc: symbolItem.jsDoc,
46 | });
47 | } else {
48 | existing.kinds.push(symbolItem.kind);
49 | if (!existing.unstable && isUnstable) {
50 | existing.unstable = true;
51 | }
52 | if (!existing.jsDoc && symbolItem.jsDoc) {
53 | existing.jsDoc = symbolItem.jsDoc;
54 | }
55 | }
56 | }
57 |
58 | const categories: Record = {};
59 | const uncategorized: ProcessedSymbol[] = [];
60 |
61 | for (const item of symbols) {
62 | if (item.category) {
63 | if (!(item.category in categories)) {
64 | categories[item.category] = [];
65 | }
66 |
67 | categories[item.category].push(item);
68 | } else {
69 | uncategorized.push(item);
70 | }
71 | }
72 |
73 | return [categories, uncategorized];
74 | }
75 |
76 | function Symbol(
77 | { children, base, active, currentSymbol, uncategorized }: {
78 | children: Child;
79 | base: URL;
80 | active: boolean;
81 | currentSymbol?: string;
82 | uncategorized?: boolean;
83 | },
84 | ) {
85 | const symbol = take(children);
86 |
87 | return (
88 |
99 |
100 |
101 | {symbol.kinds.map((kind) => docNodeKindMap[kind]())}
102 |
103 | {symbol.name}
104 |
105 | {symbol.unstable && tagVariants.unstable()}
106 |
107 | );
108 | }
109 |
110 | function Category(
111 | { children, base, name, currentSymbol }: {
112 | children: Child;
113 | name: string;
114 | base: URL;
115 | currentSymbol?: string;
116 | },
117 | ) {
118 | const items = take(children, true);
119 | const active = !!items.find(({ name }) => name === currentSymbol);
120 | return (
121 |
122 |
126 |
130 |
131 | {name}
132 |
133 |
134 |
135 | {items.sort((a, b) =>
136 | byKindValue(a.kinds[0], b.kinds[0]) || a.name.localeCompare(b.name)
137 | ).map((symbol) => (
138 |
139 | {symbol}
140 |
141 | ))}
142 |
143 | );
144 | }
145 |
146 | export function LibraryDocPanel(
147 | { children, base, currentSymbol }: {
148 | children: Child;
149 | base: URL;
150 | currentSymbol?: string;
151 | },
152 | ) {
153 | const items = take(children, true);
154 |
155 | const [categories, uncategorized] = categorize(items);
156 |
157 | const entries = [];
158 | for (
159 | const [name, symbols] of Object.entries(categories).sort(([a], [b]) =>
160 | a.localeCompare(b)
161 | )
162 | ) {
163 | entries.push(
164 |
169 | {symbols}
170 | ,
171 | );
172 | }
173 |
174 | const uncategorizedActive = !!uncategorized.find(({ name }) =>
175 | name === currentSymbol
176 | );
177 | for (const symbol of uncategorized) {
178 | entries.push(
179 |
185 | {symbol}
186 | ,
187 | );
188 | }
189 |
190 | if (entries.length === 0) {
191 | return null;
192 | }
193 | return (
194 |
195 | {entries}
196 |
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/doc/module_index_panel.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeKind, type JsDoc, tw } from "../deps.ts";
4 | import { byKindValue, getIndex } from "./doc.ts";
5 | import { services } from "../services.ts";
6 | import { style } from "../styles.ts";
7 | import { type Child, take } from "./utils.ts";
8 | import * as Icons from "../icons.tsx";
9 | import { docNodeKindMap } from "./symbol_kind.tsx";
10 |
11 | interface DocPageDirItem {
12 | kind: "dir";
13 | path: string;
14 | }
15 |
16 | export interface SymbolItem {
17 | name: string;
18 | kind: DocNodeKind;
19 | category?: string;
20 | jsDoc?: JsDoc | null;
21 | }
22 |
23 | interface DocPageModuleItem {
24 | kind: "module";
25 | path: string;
26 | items: SymbolItem[];
27 | }
28 |
29 | export type DocPageNavItem = DocPageModuleItem | DocPageDirItem;
30 |
31 | export function splitItems(
32 | _rootPath: string,
33 | items: DocPageNavItem[],
34 | ): [folders: DocPageDirItem[], modules: DocPageModuleItem[]] {
35 | const folders: DocPageDirItem[] = [];
36 | const modules: DocPageModuleItem[] = [];
37 | for (const item of items) {
38 | if (item.kind === "dir") {
39 | folders.push(item);
40 | } else {
41 | modules.push(item);
42 | }
43 | }
44 | return [folders, modules];
45 | }
46 |
47 | function Folder({ children, base, parent }: {
48 | children: Child;
49 | base: URL;
50 | parent: string;
51 | }) {
52 | const folderName = take(children);
53 | const url = new URL(base);
54 | url.pathname += folderName;
55 | const href = services.resolveHref(url);
56 | const label = folderName.slice(parent === "/" ? 1 : parent.length + 1);
57 | return (
58 |
59 |
60 | {label}
61 |
62 | );
63 | }
64 |
65 | function Module(
66 | { children, base, parent, current, currentSymbol, isIndex }: {
67 | children: Child;
68 | base: URL;
69 | parent: string;
70 | current?: string;
71 | currentSymbol?: string;
72 | isIndex?: boolean;
73 | },
74 | ) {
75 | const { path, items } = take(children);
76 | const url = new URL(base);
77 | url.pathname += path;
78 | const href = services.resolveHref(url);
79 | const label = path.slice(parent === "/" ? 1 : parent.length + 1);
80 | const active = current ? current == path : isIndex;
81 |
82 | const symbols: Record = {};
83 | for (
84 | const symbolItem of items.filter((symbol) =>
85 | symbol.kind !== "import" && symbol.kind !== "moduleDoc"
86 | ).sort((a, b) =>
87 | byKindValue(a.kind, b.kind) || a.name.localeCompare(b.name)
88 | )
89 | ) {
90 | if (Object.hasOwn(symbols, symbolItem.name)) {
91 | symbols[symbolItem.name].push(symbolItem.kind);
92 | } else {
93 | symbols[symbolItem.name] = [symbolItem.kind];
94 | }
95 | }
96 |
97 | return (
98 |
99 |
106 |
110 |
111 | {label}
112 | {isIndex && (
113 |
114 | {" "}(default module)
115 |
116 | )}
117 |
118 |
119 |
120 | {Object.entries(symbols).map(([name, kinds]) => (
121 |
129 |
130 |
131 | {kinds.map((kind) => docNodeKindMap[kind]())}
132 |
133 | {name}
134 |
135 |
136 | ))}
137 |
138 | );
139 | }
140 |
141 | export function ModuleIndexPanel(
142 | { children, path = "/", base, current, currentSymbol }: {
143 | children: Child;
144 | base: URL;
145 | path: string;
146 | current?: string;
147 | currentSymbol?: string;
148 | },
149 | ) {
150 | const items = take(children, true);
151 | const [folders, modules] = splitItems(path, items);
152 | const entries = folders.sort().map((folder) => (
153 |
154 | {folder.path}
155 |
156 | ));
157 |
158 | const moduleIndex = getIndex(modules.map((module) => module.path));
159 | if (moduleIndex) {
160 | if (current === path) {
161 | current = moduleIndex;
162 | }
163 | entries.push(
164 |
171 | {modules.find((module) => module.path === moduleIndex)!}
172 | ,
173 | );
174 | }
175 | modules.sort();
176 | for (const module of modules) {
177 | if (module.path !== moduleIndex) {
178 | entries.push(
179 |
185 | {module}
186 | ,
187 | );
188 | }
189 | }
190 | if (entries.length === 0) {
191 | return null;
192 | }
193 | return (
194 |
195 | {entries}
196 |
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/doc/doc_common.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type Accessibility,
5 | type ComponentChildren,
6 | type JsDoc as JsDocType,
7 | type JsDocTagDoc,
8 | type Location,
9 | } from "../deps.ts";
10 | import { services } from "../services.ts";
11 | import { style } from "../styles.ts";
12 | import { type Child, splitMarkdownTitle, take } from "./utils.ts";
13 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
14 | import * as Icons from "../icons.tsx";
15 |
16 | export const TARGET_RE = /(\s|[\[\]]|\.)/g;
17 |
18 | export function nameToId(kind: string, name: string) {
19 | return `${kind}_${name.replaceAll(TARGET_RE, "_")}`;
20 | }
21 |
22 | export function Anchor({ children: name }: { children: string }) {
23 | return (
24 |
30 |
31 |
32 | );
33 | }
34 |
35 | export function DocEntry(
36 | { children, tags, name, location, id, jsDoc, href, context }: {
37 | children: ComponentChildren;
38 | tags?: unknown[];
39 | name?: ComponentChildren;
40 | location: Location;
41 | id: string;
42 | jsDoc?: { doc?: string };
43 | href?: string;
44 | context: Context;
45 | },
46 | ) {
47 | const sourceHref = services.resolveSourceHref(
48 | location.filename,
49 | location.line,
50 | );
51 |
52 | return (
53 |
54 | {id}
55 |
56 |
57 |
58 |
59 | {!!tags?.length && {tags}}
60 |
61 |
62 | {name && href
63 | ? {name}
64 | : {name}}
65 | {children}
66 |
67 |
68 |
69 | {sourceHref && (
70 |
76 |
77 |
78 |
79 |
80 | )}
81 |
82 |
83 |
84 |
85 | {jsDoc}
86 |
87 |
88 |
89 | );
90 | }
91 |
92 | export function SectionTitle({ children }: { children: Child }) {
93 | const name = take(children);
94 | const id = name.replaceAll(TARGET_RE, "_");
95 | return (
96 |
97 |
98 | {name}
99 |
100 |
101 | );
102 | }
103 |
104 | export function Section(
105 | { children, title }: { children: Child; title: string },
106 | ) {
107 | const entries = take(children, true);
108 | if (entries.length === 0) {
109 | return null;
110 | }
111 |
112 | return (
113 |
114 | {title}
115 |
116 | {entries}
117 |
118 |
119 | );
120 | }
121 |
122 | export function Examples(
123 | { children, context }: {
124 | children: Child;
125 | context: Context;
126 | },
127 | ) {
128 | const jsdoc = take(children);
129 | const examples =
130 | (jsdoc?.tags?.filter((tag) => tag.kind === "example" && tag.doc) ??
131 | []) as JsDocTagDoc[];
132 |
133 | if (examples.length === 0) {
134 | return null;
135 | }
136 |
137 | return (
138 |
139 | Examples
140 |
141 | {examples.map((example, i) => (
142 | {example.doc!}
143 | ))}
144 |
145 |
146 | );
147 | }
148 |
149 | function Example(
150 | { children, n, context }: {
151 | children: Child;
152 | n: number;
153 | context: Context;
154 | },
155 | ) {
156 | const md = take(children);
157 | const [summary, body] = splitMarkdownTitle(md);
158 |
159 | const id = `example_${n}`;
160 |
161 | return (
162 |
163 | {id}
164 |
165 |
166 |
170 |
171 | {summary || `Example ${n + 1}`}
172 |
173 |
174 |
175 |
176 | {body}
177 |
178 |
179 |
180 | );
181 | }
182 |
183 | export const tagColors = {
184 | purple: ["[#7B61FF1A]", "[#7B61FF]"],
185 | cyan: ["[#0CAFC619]", "[#0CAFC6]"],
186 | gray: ["gray-100", "gray-400"],
187 | } as const;
188 |
189 | export function Tag(
190 | { children, color, large }: {
191 | children: ComponentChildren;
192 | color: keyof typeof tagColors;
193 | large?: boolean;
194 | },
195 | ) {
196 | const [bg, text] = tagColors[color];
197 | return (
198 |
203 | {children}
204 |
205 | );
206 | }
207 |
208 | export const tagVariants = {
209 | reExportLg: () => Re-export ,
210 | deprecatedLg: () => (
211 |
212 |
213 | Deprecated
214 |
215 | ),
216 | deprecated: () => deprecated ,
217 | abstractLg: () => Abstract ,
218 | abstract: () => abstract ,
219 | unstableLg: () => Unstable ,
220 | unstable: () => unstable ,
221 | readonly: () => readonly ,
222 | writeonly: () => writeonly ,
223 | optional: () => optional ,
224 | } as const;
225 |
226 | export function getAccessibilityTag(accessibility?: Accessibility) {
227 | if (!accessibility || accessibility === "public") {
228 | return null;
229 | }
230 | return {accessibility} ;
231 | }
232 |
--------------------------------------------------------------------------------
/doc/symbol_doc.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | type DocNodeClass,
6 | type DocNodeFunction,
7 | type DocNodeInterface,
8 | type DocNodeTypeAlias,
9 | type JsDocTagDoc,
10 | type JsDocTagTags,
11 | } from "../deps.ts";
12 | import { byKind } from "./doc.ts";
13 | import { DocBlock } from "./doc_block.tsx";
14 | import { Tag, tagVariants } from "./doc_common.tsx";
15 | import * as Icons from "../icons.tsx";
16 | import { services } from "../services.ts";
17 | import { style } from "../styles.ts";
18 | import { Usage } from "./usage.tsx";
19 | import {
20 | type Child,
21 | isAbstract,
22 | isDeprecated,
23 | processProperty,
24 | take,
25 | } from "./utils.ts";
26 | import { DocTitle } from "./doc_title.tsx";
27 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
28 |
29 | function isTypeOnly(
30 | docNodes: DocNode[],
31 | ): docNodes is (DocNodeInterface | DocNodeTypeAlias)[] {
32 | return docNodes.every(({ kind }) =>
33 | kind === "interface" || kind === "typeAlias"
34 | );
35 | }
36 |
37 | export function SymbolDoc(
38 | { children, name, library = false, property, ...context }: {
39 | children: Child;
40 | name: string;
41 | library?: boolean;
42 | property?: string;
43 | } & Pick,
44 | ) {
45 | const docNodes = [...take(children, true)];
46 | docNodes.sort(byKind);
47 | let splitNodes: Record = {};
48 | let isReExport = false;
49 | for (const docNode of docNodes) {
50 | if (docNode.kind === "import") {
51 | isReExport = true;
52 | continue;
53 | }
54 | if (!(docNode.kind in splitNodes)) {
55 | splitNodes[docNode.kind] = [];
56 | }
57 | splitNodes[docNode.kind].push(docNode);
58 | }
59 |
60 | let propertyName: string | undefined;
61 | if (property && ("class" in splitNodes)) {
62 | // TODO(@crowlKats): type parameters declared in the class are not available
63 | // in the drilled down method
64 | const [propName, isPrototype] = processProperty(property);
65 |
66 | const classNode = (splitNodes["class"] as DocNodeClass[])[0];
67 | const functionNodes: DocNodeFunction[] = classNode.classDef.methods.filter((
68 | def,
69 | ) => def.name === propName && (isPrototype === !def.isStatic)).map(
70 | (def) => {
71 | return {
72 | declarationKind: classNode.declarationKind,
73 | functionDef: def.functionDef,
74 | jsDoc: def.jsDoc,
75 | kind: "function",
76 | location: def.location,
77 | name: def.name,
78 | };
79 | },
80 | );
81 |
82 | if (functionNodes.length !== 0) {
83 | splitNodes = { function: functionNodes };
84 | propertyName = property;
85 | }
86 | }
87 |
88 | const showUsage = !(context.url.href.endsWith(".d.ts") || library);
89 |
90 | return (
91 |
92 | {Object.values(splitNodes).map((nodes) => (
93 |
100 | {nodes}
101 |
102 | ))}
103 |
104 | );
105 | }
106 |
107 | function Symbol(
108 | { children, showUsage, property, name, isReExport, context }: {
109 | children: Child;
110 | showUsage: boolean;
111 | property?: string;
112 | name: string;
113 | isReExport: boolean;
114 | context: Context;
115 | },
116 | ) {
117 | const docNodes = take(children, true);
118 | const jsDoc = docNodes.map(({ jsDoc }) => jsDoc).find((jsDoc) => !!jsDoc);
119 | const isFunction = docNodes[0].kind === "function";
120 |
121 | const tags = [];
122 |
123 | if (isReExport) {
124 | tags.push(tagVariants.reExportLg());
125 | }
126 |
127 | const jsDocTags: string[] = docNodes.flatMap(({ jsDoc }) =>
128 | (jsDoc?.tags?.filter(({ kind }) => kind === "tags") as
129 | | JsDocTagTags[]
130 | | undefined)?.flatMap(({ tags }) => tags) ?? []
131 | );
132 |
133 | const permTags = jsDocTags.filter((tag, i) =>
134 | tag.startsWith("allow-") && jsDocTags.indexOf(tag) === i
135 | );
136 | if (permTags.length !== 0) {
137 | tags.push(
138 |
139 |
140 | {permTags.map((tag, i) => (
141 | <>
142 | {i !== 0 && }
143 | {tag}
144 | >
145 | ))}
146 |
147 | ,
148 | );
149 | }
150 |
151 | if (jsDocTags.includes("unstable")) {
152 | tags.push(tagVariants.unstableLg());
153 | }
154 |
155 | if (isAbstract(docNodes[0])) {
156 | tags.push(tagVariants.abstractLg());
157 | }
158 |
159 | let deprecated: JsDocTagDoc | undefined;
160 | if (docNodes.every(isDeprecated)) {
161 | deprecated = isDeprecated(docNodes[0]);
162 | if (deprecated) {
163 | tags.push(tagVariants.deprecatedLg());
164 | }
165 | }
166 |
167 | const lastSymbolIndex = name.lastIndexOf(".");
168 | context.namespace = lastSymbolIndex !== -1
169 | ? name.slice(0, lastSymbolIndex)
170 | : undefined;
171 |
172 | return (
173 |
174 |
175 |
176 |
177 | {docNodes[0]}
178 |
179 |
180 | {tags.length !== 0 && (
181 |
182 | {tags}
183 |
184 | )}
185 |
186 |
194 |
195 |
196 |
197 |
198 | {deprecated?.doc && (
199 |
200 |
201 |
202 |
203 | Deprecated
204 |
205 |
206 |
207 |
208 | {deprecated.doc}
209 |
210 |
211 | )}
212 |
213 |
214 | {showUsage && (
215 |
220 | )}
221 | {!isFunction && {jsDoc} }
222 |
223 |
224 |
225 | {docNodes}
226 |
227 |
228 | );
229 | }
230 |
--------------------------------------------------------------------------------
/doc/module_doc.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type ComponentChildren, type DocNode } from "../deps.ts";
4 | import { Examples, SectionTitle, tagVariants } from "./doc_common.tsx";
5 | import * as Icons from "../icons.tsx";
6 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
7 | import { services } from "../services.ts";
8 | import { style } from "../styles.ts";
9 | import { Usage } from "./usage.tsx";
10 | import * as SymbolKind from "./symbol_kind.tsx";
11 | import {
12 | asCollection,
13 | byName,
14 | type Child,
15 | DocNodeCollection,
16 | DocNodeTupleArray,
17 | isAbstract,
18 | isDeprecated,
19 | maybe,
20 | take,
21 | } from "./utils.ts";
22 |
23 | function Entry(
24 | { children, icon, context }: {
25 | children: Child<[label: string, node: Node]>;
26 | icon: ComponentChildren;
27 | context: Context;
28 | },
29 | ) {
30 | const [label, node] = take(children, true);
31 | const name = context.namespace ? `${context.namespace}.${label}` : label;
32 | const href = services.resolveHref(context.url, name);
33 |
34 | return (
35 |
36 |
37 |
38 | {icon}
39 | {name}
40 | {maybe(isAbstract(node), tagVariants.abstract())}
41 | {maybe(isDeprecated(node), tagVariants.deprecated())}
42 |
43 |
44 |
45 |
46 | {node.jsDoc?.doc}
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | function Section(
54 | { children, title, icon, context }: {
55 | children: Child>;
56 | title: string;
57 | icon: ComponentChildren;
58 | context: Context;
59 | },
60 | ) {
61 | const tuples = take(children, true, true);
62 | const displayed = new Set();
63 | const items = tuples.sort(byName).map(([label, node]) => {
64 | if (displayed.has(label)) {
65 | return null;
66 | }
67 | displayed.add(label);
68 | return (
69 |
70 | {[label, node]}
71 |
72 | );
73 | });
74 | return (
75 |
76 | {title}
77 | {items}
78 |
79 | );
80 | }
81 |
82 | export function DocTypeSections(
83 | { children, context }: {
84 | children: Child;
85 | context: Context;
86 | },
87 | ) {
88 | const collection = take(children);
89 | return (
90 | <>
91 | {collection.namespace && (
92 | }
95 | context={context}
96 | >
97 | {collection.namespace}
98 |
99 | )}
100 | {collection.class && (
101 | }
104 | context={context}
105 | >
106 | {collection.class}
107 |
108 | )}
109 | {collection.enum && (
110 | }
113 | context={context}
114 | >
115 | {collection.enum}
116 |
117 | )}
118 | {collection.variable && (
119 | }
122 | context={context}
123 | >
124 | {collection.variable}
125 |
126 | )}
127 | {collection.function && (
128 | }
131 | context={context}
132 | >
133 | {collection.function}
134 |
135 | )}
136 | {collection.interface && (
137 | }
140 | context={context}
141 | >
142 | {collection.interface}
143 |
144 | )}
145 | {collection.typeAlias && (
146 | }
149 | context={context}
150 | >
151 | {collection.typeAlias}
152 |
153 | )}
154 | >
155 | );
156 | }
157 |
158 | export function ModuleDoc(
159 | { children, sourceUrl, ...context }: {
160 | children: Child;
161 | sourceUrl: string;
162 | } & Pick,
163 | ) {
164 | const docNodes = take(children, true);
165 | const isEmpty = docNodes.length === 0;
166 | const hasExports = docNodes.some(({ declarationKind, kind }) =>
167 | kind !== "moduleDoc" && declarationKind === "export"
168 | );
169 | const collection = asCollection(docNodes);
170 | const jsDoc = collection.moduleDoc?.[0][1].jsDoc;
171 | const deprecated = isDeprecated({ jsDoc });
172 |
173 | return (
174 |
175 |
176 | {/* TODO: add module name */}
177 |
182 |
183 |
184 |
185 |
186 |
187 |
188 | {deprecated && (
189 |
190 |
191 |
192 |
193 | Deprecated
194 |
195 |
196 |
197 | {deprecated.doc &&
198 | (
199 |
200 | {deprecated.doc}
201 |
202 | )}
203 |
204 | )}
205 |
206 | {isEmpty || hasExports ? : undefined}
207 |
208 | {jsDoc}
209 | {jsDoc}
210 |
211 | {isEmpty
212 | ? (
213 |
214 | The documentation for this module is currently unavailable.
215 |
216 | )
217 | : hasExports
218 | ? (
219 |
220 | {collection}
221 |
222 | )
223 | : (
224 |
225 | This module does not provide any exports.
226 |
227 | )}
228 |
229 |
230 |
231 | );
232 | }
233 |
--------------------------------------------------------------------------------
/doc/functions.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | apply,
5 | css,
6 | type DocNodeFunction,
7 | type FunctionDef,
8 | type JsDocTagParam,
9 | type JsDocTagReturn,
10 | type JsDocTagValued,
11 | tw,
12 | } from "../deps.ts";
13 | import {
14 | DocEntry,
15 | Examples,
16 | nameToId,
17 | Section,
18 | tagVariants,
19 | } from "./doc_common.tsx";
20 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
21 | import { paramName, Params } from "./params.tsx";
22 | import { style } from "../styles.ts";
23 | import { DocTypeParamsSummary, TypeDef, TypeParamsDoc } from "./types.tsx";
24 | import { type Child, isDeprecated, take } from "./utils.ts";
25 | import * as Icons from "../icons.tsx";
26 |
27 | export function DocFunctionSummary({
28 | children,
29 | context,
30 | }: {
31 | children: Child;
32 | context: Context;
33 | }) {
34 | const def = take(children, true);
35 |
36 | return (
37 | <>
38 |
39 | {def.typeParams}
40 |
41 | (
42 |
43 | {def.params}
44 |
45 | )
46 | {def.returnType && (
47 |
48 | :{" "}
49 |
50 | {def.returnType}
51 |
52 |
53 | )}
54 | >
55 | );
56 | }
57 |
58 | function DocFunctionOverload({
59 | children,
60 | i,
61 | context,
62 | }: {
63 | children: Child;
64 | i: number;
65 | context: Context;
66 | }) {
67 | const def = take(children, true);
68 |
69 | if (def.functionDef.hasBody && i !== 0) {
70 | return null;
71 | }
72 |
73 | context.typeParams = def.functionDef.typeParams.map(({ name }) => name);
74 | const overloadId = nameToId("function", `${def.name}_${i}`);
75 |
76 | const deprecated = isDeprecated(def);
77 |
78 | return (
79 |
120 | );
121 | }
122 |
123 | function DocFunction(
124 | { children, n, context }: {
125 | children: Child;
126 | n: number;
127 | context: Context;
128 | },
129 | ) {
130 | const def = take(children);
131 | context.typeParams = def.functionDef.typeParams.map(({ name }) => name);
132 |
133 | const overloadId = nameToId("function", `${def.name}_${n}`);
134 | const tags = [];
135 |
136 | if (isDeprecated(def)) {
137 | tags.push(tagVariants.deprecated());
138 | }
139 |
140 | const paramDocs: JsDocTagParam[] =
141 | (def.jsDoc?.tags?.filter(({ kind }) => kind === "param") as
142 | | JsDocTagParam[]
143 | | undefined) ??
144 | [];
145 |
146 | const parameters = def.functionDef.params.map((param, i) => {
147 | const name = paramName(param, i);
148 | const id = nameToId("function", `${def.name}_${n}_parameters_${name}`);
149 |
150 | const defaultValue = ((def.jsDoc?.tags?.find(({ kind }) =>
151 | kind === "default"
152 | ) as JsDocTagValued | undefined)?.value) ??
153 | (param.kind === "assign" ? param.right : undefined);
154 |
155 | const type = param.kind === "assign" ? param.left.tsType : param.tsType;
156 |
157 | const tags = [];
158 | if (("optional" in param && param.optional) || defaultValue) {
159 | tags.push(tagVariants.optional());
160 | }
161 |
162 | return (
163 |
171 | {type && (
172 |
173 | :{" "}
174 |
175 | {type}
176 |
177 |
178 | )}
179 | {defaultValue && (
180 |
181 | {" = "}
182 | {defaultValue}
183 |
184 | )}
185 |
186 | );
187 | });
188 |
189 | const returnDoc = def.jsDoc?.tags?.find(({ kind }) =>
190 | kind === "return"
191 | ) as (JsDocTagReturn | undefined);
192 | const returnId = nameToId("function", `${def.name}_${n}_return`);
193 |
194 | return (
195 |
196 | {def.jsDoc}
197 |
198 | {def.jsDoc}
199 |
200 |
201 | {def.functionDef.typeParams}
202 |
203 |
204 | {parameters}
205 |
206 | {def.functionDef.returnType && (
207 |
208 | {[
209 |
215 |
218 | {def.functionDef.returnType}
219 |
220 | ,
221 | ]}
222 |
223 | )}
224 |
225 | );
226 | }
227 |
228 | export function DocBlockFunction(
229 | { children, context }: {
230 | children: Child;
231 | context: Context;
232 | },
233 | ) {
234 | const defs = take(children, true);
235 |
236 | const items = defs.map((def, i) => {
237 | if (def.functionDef.hasBody && i !== 0) {
238 | return null;
239 | }
240 |
241 | return {def} ;
242 | });
243 |
244 | return (
245 |
246 | {defs.map((def, i) => {
247 | if (def.functionDef.hasBody && i !== 0) {
248 | return null;
249 | }
250 |
251 | const id = nameToId("function", def.name);
252 | const overloadId = nameToId("function", `${def.name}_${i}`);
253 |
254 | const deprecated = isDeprecated(def);
255 |
256 | return (
257 | :not(#${overloadId}_div)`]:
264 | apply`hidden`,
265 | [`&:checked ~ div:first-of-type > label[for='${overloadId}']`]:
266 | apply`border-2 cursor-unset ${
267 | deprecated
268 | ? "bg-[#D256460C] border-red-600"
269 | : "bg-[#056CF00C] border-blue-600"
270 | }`,
271 | [`&:checked ~ div:first-of-type > label[for='${overloadId}'] > div`]:
272 | apply`-m-px`,
273 | })
274 | }`}
275 | checked={i === 0}
276 | />
277 | );
278 | })}
279 |
280 | {defs.map((def, i) => (
281 |
282 | {def}
283 |
284 | ))}
285 |
286 |
287 | {items}
288 |
289 | );
290 | }
291 |
--------------------------------------------------------------------------------
/doc/interfaces.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type ClassIndexSignatureDef,
5 | type DocNodeInterface,
6 | type InterfaceCallSignatureDef,
7 | type InterfaceIndexSignatureDef,
8 | type InterfaceMethodDef,
9 | type InterfacePropertyDef,
10 | type JsDocTagValued,
11 | } from "../deps.ts";
12 | import {
13 | Anchor,
14 | DocEntry,
15 | Examples,
16 | nameToId,
17 | Section,
18 | Tag,
19 | tagVariants,
20 | } from "./doc_common.tsx";
21 | import { Context } from "./markdown.tsx";
22 | import { Params } from "./params.tsx";
23 | import { style } from "../styles.ts";
24 | import { DocTypeParamsSummary, TypeDef, TypeParamsDoc } from "./types.tsx";
25 | import { type Child, isDeprecated, maybe, take } from "./utils.ts";
26 |
27 | type IndexSignatureDef =
28 | | ClassIndexSignatureDef
29 | | InterfaceIndexSignatureDef;
30 |
31 | function CallSignaturesDoc(
32 | { children, context }: {
33 | children: Child;
34 | context: Context;
35 | },
36 | ) {
37 | const defs = take(children, true);
38 | if (!defs.length) {
39 | return null;
40 | }
41 | const items = defs.map(
42 | ({ typeParams, params, tsType, jsDoc, location }, i) => {
43 | const id = nameToId("call_sig", String(i));
44 | const tags = [];
45 | if (isDeprecated({ jsDoc })) {
46 | tags.push(tagVariants.deprecated());
47 | }
48 | return (
49 |
56 |
57 | {typeParams}
58 | (
59 | {params}
60 | ){tsType && (
61 | <>
62 | :{" "}
63 |
64 | {tsType}
65 |
66 | >
67 | )}
68 |
69 | );
70 | },
71 | );
72 | return {items} ;
73 | }
74 |
75 | export function IndexSignaturesDoc(
76 | { children, context }: {
77 | children: Child;
78 | context: Context;
79 | },
80 | ) {
81 | const defs = take(children, true);
82 | if (!defs.length) {
83 | return null;
84 | }
85 | const items = defs.map(({ readonly, params, tsType }, i) => {
86 | const id = nameToId("index_sig", String(i));
87 | return (
88 |
89 | {id}
90 | {maybe(
91 | readonly,
92 | readonly{" "},
93 | )}[
94 | {params}
95 | ]{tsType && (
96 |
97 | :{" "}
98 |
99 | {tsType}
100 |
101 |
102 | )}
103 |
104 | );
105 | });
106 |
107 | return {items} ;
108 | }
109 |
110 | function MethodsDoc(
111 | { children, context }: {
112 | children: Child;
113 | context: Context;
114 | },
115 | ) {
116 | const defs = take(children, true);
117 | if (!defs.length) {
118 | return null;
119 | }
120 | const items = defs.map(
121 | (
122 | {
123 | name,
124 | kind,
125 | location,
126 | jsDoc,
127 | computed,
128 | optional,
129 | params,
130 | returnType,
131 | typeParams,
132 | },
133 | i,
134 | ) => {
135 | const id = nameToId("method", `${name}_${i}`);
136 |
137 | const tags = [];
138 | if (kind !== "method") {
139 | tags.push({kind} );
140 | }
141 | if (optional) {
142 | tags.push(tagVariants.optional());
143 | }
144 | if (isDeprecated({ jsDoc })) {
145 | tags.push(tagVariants.deprecated());
146 | }
147 |
148 | return (
149 | new
155 | : computed
156 | ? `[${name}]`
157 | : name}
158 | jsDoc={jsDoc}
159 | context={context}
160 | >
161 |
162 | {typeParams}
163 |
164 | (
165 | {params}
166 | )
167 | {returnType && (
168 |
169 | :{" "}
170 |
171 | {returnType}
172 |
173 |
174 | )}
175 |
176 | );
177 | },
178 | );
179 | return {items} ;
180 | }
181 |
182 | function PropertiesDoc(
183 | { children, context }: {
184 | children: Child;
185 | context: Context;
186 | },
187 | ) {
188 | const defs = take(children, true);
189 | if (!defs.length) {
190 | return null;
191 | }
192 | const items = defs.map(
193 | (
194 | {
195 | name,
196 | location,
197 | jsDoc,
198 | readonly,
199 | computed,
200 | optional,
201 | tsType,
202 | },
203 | ) => {
204 | const id = nameToId("prop", name);
205 |
206 | const tags = [];
207 | if (readonly) {
208 | tags.push(tagVariants.readonly());
209 | }
210 | if (optional) {
211 | tags.push(tagVariants.optional());
212 | }
213 | if (isDeprecated({ jsDoc })) {
214 | tags.push(tagVariants.deprecated());
215 | }
216 |
217 | const defaultValue =
218 | (jsDoc?.tags?.find(({ kind }) => kind === "default") as
219 | | JsDocTagValued
220 | | undefined)?.value;
221 |
222 | return (
223 |
231 | {tsType && (
232 | <>
233 | :{" "}
234 |
235 | {tsType}
236 |
237 | >
238 | )}
239 | {defaultValue && (
240 |
241 | {" = "}
242 | {defaultValue}
243 |
244 | )}
245 |
246 | );
247 | },
248 | );
249 |
250 | return {items} ;
251 | }
252 |
253 | export function DocSubTitleInterface(
254 | { children, context }: {
255 | children: Child;
256 | context: Context;
257 | },
258 | ) {
259 | const { interfaceDef } = take(children);
260 |
261 | if (interfaceDef.extends.length === 0) {
262 | return null;
263 | }
264 |
265 | return (
266 |
267 | {" implements "}
268 | {interfaceDef.extends.map((typeDef, i) => (
269 | <>
270 |
271 | {typeDef}
272 |
273 | {i !== (interfaceDef.extends.length - 1) && ,{" "}}
274 | >
275 | ))}
276 |
277 | );
278 | }
279 |
280 | export function DocBlockInterface(
281 | { children, context }: {
282 | children: Child;
283 | context: Context;
284 | },
285 | ) {
286 | const def = take(children);
287 | context.typeParams = def.interfaceDef.typeParams.map(({ name }) => name);
288 | return (
289 |
290 | {def.jsDoc}
291 |
292 |
293 | {def.interfaceDef.typeParams}
294 |
295 |
296 |
297 | {def.interfaceDef.indexSignatures}
298 |
299 |
300 |
301 | {def.interfaceDef.callSignatures}
302 |
303 |
304 |
305 | {def.interfaceDef.properties}
306 |
307 |
308 |
309 | {def.interfaceDef.methods}
310 |
311 |
312 | );
313 | }
314 |
--------------------------------------------------------------------------------
/doc/utils.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | type DocNodeClass,
6 | type DocNodeEnum,
7 | type DocNodeFunction,
8 | type DocNodeImport,
9 | type DocNodeInterface,
10 | type DocNodeModuleDoc,
11 | type DocNodeNamespace,
12 | type DocNodeTypeAlias,
13 | type DocNodeVariable,
14 | type JsDoc,
15 | type JsDocTagDoc,
16 | } from "../deps.ts";
17 |
18 | /** Some JSX libraries (notably nano-jsx) have strange handling of the
19 | * child element and don't have good typings when creating a functional
20 | * component. This type and the function {@linkcode take} abstract this
21 | * away. */
22 | export type Child = T | [T];
23 |
24 | export interface DocNodeCollection {
25 | moduleDoc?: DocNodeTupleArray;
26 | import?: DocNodeTupleArray;
27 | namespace?: DocNodeTupleArray;
28 | class?: DocNodeTupleArray;
29 | enum?: DocNodeTupleArray;
30 | variable?: DocNodeTupleArray;
31 | function?: DocNodeTupleArray;
32 | interface?: DocNodeTupleArray;
33 | typeAlias?: DocNodeTupleArray;
34 | }
35 |
36 | export type DocNodeTupleArray = [label: string, node: N][];
37 |
38 | interface ParsedURL {
39 | registry: string;
40 | org?: string;
41 | package?: string;
42 | version?: string;
43 | module?: string;
44 | }
45 |
46 | function appendCollection(
47 | collection: DocNodeCollection,
48 | nodes: DocNode[],
49 | path?: string,
50 | includePrivate = false,
51 | ) {
52 | for (const node of nodes) {
53 | if (includePrivate || node.declarationKind !== "private") {
54 | if (node.kind === "namespace" && !node.namespaceDef.elements.length) {
55 | continue;
56 | }
57 | if (node.kind === "moduleDoc" && path) {
58 | continue;
59 | }
60 | const docNodes: DocNodeTupleArray = collection[node.kind] ??
61 | (collection[node.kind] = []);
62 | const label = path ? `${path}.${node.name}` : node.name;
63 | docNodes.push([label, node]);
64 | if (node.kind === "namespace") {
65 | appendCollection(
66 | collection,
67 | node.namespaceDef.elements,
68 | label,
69 | includePrivate,
70 | );
71 | }
72 | }
73 | }
74 | }
75 |
76 | export function asCollection(
77 | nodes: DocNode[],
78 | path?: string,
79 | includePrivate = false,
80 | ): DocNodeCollection {
81 | const collection: DocNodeCollection = {};
82 | appendCollection(collection, nodes, path, includePrivate);
83 | return collection;
84 | }
85 |
86 | export function assert(
87 | cond: unknown,
88 | message = "Assertion error",
89 | ): asserts cond {
90 | if (!cond) {
91 | throw new Error(message);
92 | }
93 | }
94 |
95 | export function byName(
96 | a: [label: string, node: Node],
97 | b: [label: string, node: Node],
98 | ) {
99 | return a[0].localeCompare(b[0]);
100 | }
101 |
102 | /** Convert a string into a camelCased string. */
103 | export function camelize(str: string): string {
104 | return str.split(/[\s_\-]+/).map((word, index) =>
105 | index === 0
106 | ? word.toLowerCase()
107 | : `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`
108 | ).join("");
109 | }
110 |
111 | /** Convert a camelCased string into a normal string. */
112 | export function decamelize(str: string): string {
113 | return str.split("").map((char) =>
114 | /[A-Z]/.test(char) ? ` ${char.toLowerCase()}` : char
115 | ).join("");
116 | }
117 |
118 | export function isAbstract(node: DocNode) {
119 | if (node.kind === "class") {
120 | return node.classDef.isAbstract;
121 | }
122 | return false;
123 | }
124 |
125 | export function isDeprecated(
126 | node?: { jsDoc?: JsDoc },
127 | ): JsDocTagDoc | undefined {
128 | if (node && node.jsDoc && node.jsDoc.tags) {
129 | return node.jsDoc.tags.find(({ kind }) => kind === "deprecated") as
130 | | JsDocTagDoc
131 | | undefined;
132 | }
133 | }
134 |
135 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
136 | * which defaults to `undefined`. */
137 | export function maybe(cond: unknown, isTrue: T): T | null;
138 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
139 | * which defaults to `undefined`. */
140 | export function maybe(cond: unknown, isTrue: T, isFalse: F): T | F;
141 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
142 | * which defaults to `undefined`. */
143 | export function maybe(
144 | cond: unknown,
145 | isTrue: T,
146 | isFalse?: F,
147 | ): T | F | null {
148 | return cond ? isTrue : isFalse ?? null;
149 | }
150 |
151 | /** Patterns of "registries" which will be parsed to be displayed in a more
152 | * human readable way. */
153 | const patterns = {
154 | "deno.land/x": [
155 | new URLPattern(
156 | "https://deno.land/x/:pkg([^@/]+){@}?:ver?/:mod*",
157 | ),
158 | ],
159 | "deno.land/std": [new URLPattern("https://deno.land/std{@}?:ver?/:mod*")],
160 | "nest.land": [new URLPattern("https://x.nest.land/:pkg([^@/]+)@:ver/:mod*")],
161 | "crux.land": [new URLPattern("https://crux.land/:pkg([^@/]+)@:ver")],
162 | "github.com": [
163 | new URLPattern(
164 | "https://raw.githubusercontent.com/:org/:pkg/:ver/:mod*",
165 | ),
166 | // https://github.com/denoland/deno_std/raw/main/http/mod.ts
167 | new URLPattern(
168 | "https://github.com/:org/:pkg/raw/:ver/:mod*",
169 | ),
170 | ],
171 | "gist.github.com": [
172 | new URLPattern(
173 | "https://gist.githubusercontent.com/:org/:pkg/raw/:ver/:mod*",
174 | ),
175 | ],
176 | "esm.sh": [
177 | new URLPattern(
178 | "http{s}?://esm.sh/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
179 | ),
180 | // https://cdn.esm.sh/v58/firebase@9.4.1/database/dist/database/index.d.ts
181 | new URLPattern(
182 | "http{s}?://cdn.esm.sh/:regver*/:org(@[^/]+)?/:pkg([^@/]+)@:ver/:mod*",
183 | ),
184 | ],
185 | "skypack.dev": [
186 | new URLPattern({
187 | protocol: "https",
188 | hostname: "cdn.skypack.dev",
189 | pathname: "/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
190 | search: "*",
191 | }),
192 | // https://cdn.skypack.dev/-/@firebase/firestore@v3.4.3-A3UEhS17OZ2Vgra7HCZF/dist=es2019,mode=types/dist/index.d.ts
193 | new URLPattern(
194 | "https://cdn.skypack.dev/-/:org(@[^/]+)?/:pkg([^@/]+)@:ver([^-]+):hash/:path*",
195 | ),
196 | ],
197 | "unpkg.com": [
198 | new URLPattern(
199 | "https://unpkg.com/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
200 | ),
201 | ],
202 | };
203 |
204 | /** Take a string URL and attempt to pattern match it against a known registry
205 | * and returned the parsed structure. */
206 | export function parseURL(url: URL): ParsedURL | undefined {
207 | for (const [registry, pattern] of Object.entries(patterns)) {
208 | for (const pat of pattern) {
209 | const match = pat.exec(url);
210 | if (match) {
211 | let { pathname: { groups: { regver, org, pkg, ver, mod } } } = match;
212 | if (registry === "gist.github.com") {
213 | pkg = pkg?.substring(0, 7);
214 | ver = ver?.substring(0, 7);
215 | }
216 | return {
217 | registry: regver ? `${registry} @ ${regver}` : registry,
218 | org: org || undefined,
219 | package: pkg || undefined,
220 | version: ver || undefined,
221 | module: mod || undefined,
222 | };
223 | }
224 | }
225 | }
226 | }
227 |
228 | /** A utility function that inspects a value, and if the value is an array,
229 | * returns the first element of the array, otherwise returns the value. This is
230 | * used to deal with the ambiguity around children properties with nano_jsx. */
231 | export function take(
232 | value: Child,
233 | itemIsArray = false,
234 | isArrayOfArrays = false,
235 | ): T {
236 | if (itemIsArray) {
237 | if (isArrayOfArrays) {
238 | return Array.isArray(value) && Array.isArray(value[0]) &&
239 | Array.isArray(value[0][0])
240 | ? value[0]
241 | : value as T;
242 | } else {
243 | return Array.isArray(value) && Array.isArray(value[0])
244 | ? value[0]
245 | : value as T;
246 | }
247 | } else {
248 | return Array.isArray(value) ? value[0] : value;
249 | }
250 | }
251 |
252 | /**
253 | * Splits a markdown file by its first line or first codeblock, depending on
254 | * which is first.
255 | *
256 | * @param markdown
257 | */
258 | export function splitMarkdownTitle(
259 | markdown: string,
260 | ): [summary: string, body: string] {
261 | let newlineIndex = markdown.indexOf("\n\n");
262 | let codeblockIndex = markdown.indexOf("```");
263 | if (newlineIndex == -1) {
264 | newlineIndex = Infinity;
265 | }
266 | if (codeblockIndex == -1) {
267 | codeblockIndex = Infinity;
268 | }
269 | const splitIndex = Math.min(newlineIndex, codeblockIndex);
270 | const summary = markdown.slice(0, splitIndex).trim();
271 | const body = markdown.slice(splitIndex).trim();
272 | return [summary, body];
273 | }
274 |
275 | /**
276 | * Get the property from a property string.
277 | * @return the property and whether it is part of the prototype or static.
278 | */
279 | export function processProperty(
280 | property: string,
281 | ): [property: string, isPrototype: boolean] {
282 | const isPrototype = property.startsWith("prototype.");
283 | const propName = isPrototype ? property.slice(10) : property;
284 | return [propName, isPrototype];
285 | }
286 |
--------------------------------------------------------------------------------
/_showcase/data.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNodeClass,
5 | type DocNodeEnum,
6 | type DocNodeFunction,
7 | type DocNodeInterface,
8 | type DocNodeNamespace,
9 | type DocNodeTypeAlias,
10 | } from "../deps.ts";
11 |
12 | export const classNode: DocNodeClass = {
13 | name: "AClass",
14 | kind: "class",
15 | location: {
16 | filename: "https://deno.land/x/mod/mod.ts",
17 | line: 23,
18 | col: 0,
19 | },
20 | declarationKind: "export",
21 | jsDoc: {
22 | doc: "a deprecated class",
23 | tags: [{ kind: "deprecated", doc: "don't use this" }, {
24 | kind: "example",
25 | doc:
26 | '```ignore\nimport { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/buffer.ts";\nconst response = await fetch("https://example.com");\nconst rangedStream = response.body!\n .pipeThrough(new ByteSliceStream(3, 8));\n```',
27 | }, {
28 | kind: "example",
29 | doc:
30 | 'hello world\n\nmore body text\n```ts\nimport { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/buffer.ts";\nconst response = await fetch("https://example.com");\nconst rangedStream = response.body!\n .pipeThrough(new ByteSliceStream(3, 8));\n```',
31 | }],
32 | },
33 | classDef: {
34 | isAbstract: false,
35 | constructors: [{
36 | name: "new",
37 | params: [{
38 | kind: "identifier",
39 | name: "a",
40 | tsType: {
41 | kind: "keyword",
42 | keyword: "string",
43 | repr: "string",
44 | },
45 | optional: false,
46 | }],
47 | location: {
48 | filename: "https://deno.land/x/mod/mod.ts",
49 | line: 25,
50 | col: 2,
51 | },
52 | jsDoc: {
53 | doc: "Some sort of doc `here`. **love it**",
54 | tags: [{
55 | kind: "deprecated",
56 | doc: "some deprecated doc",
57 | }, {
58 | kind: "param",
59 | name: "a",
60 | doc: "some param _doc_",
61 | }],
62 | },
63 | }],
64 | properties: [{
65 | tsType: {
66 | kind: "keyword",
67 | keyword: "number",
68 | repr: "number",
69 | },
70 | readonly: false,
71 | optional: true,
72 | isAbstract: false,
73 | isStatic: false,
74 | name: "someNumber",
75 | decorators: [{
76 | name: "log",
77 | location: {
78 | filename: "https://deno.land/x/mod/mod.ts",
79 | line: 30,
80 | col: 2,
81 | },
82 | }],
83 | location: {
84 | filename: "https://deno.land/x/mod/mod.ts",
85 | line: 31,
86 | col: 2,
87 | },
88 | }, {
89 | jsDoc: {
90 | doc: "some property JSDoc",
91 | tags: [{ kind: "deprecated" }],
92 | },
93 | tsType: {
94 | kind: "keyword",
95 | keyword: "string",
96 | repr: "string",
97 | },
98 | readonly: true,
99 | accessibility: "protected",
100 | optional: false,
101 | isAbstract: false,
102 | isStatic: false,
103 | name: "prop1",
104 | location: {
105 | filename: "https://deno.land/x/mod/mod.ts",
106 | line: 30,
107 | col: 2,
108 | },
109 | }],
110 | indexSignatures: [],
111 | methods: [{
112 | kind: "getter",
113 | name: "value",
114 | optional: false,
115 | isAbstract: false,
116 | isStatic: false,
117 | functionDef: {
118 | params: [],
119 | returnType: {
120 | kind: "keyword",
121 | keyword: "string",
122 | repr: "string",
123 | },
124 | isAsync: false,
125 | isGenerator: false,
126 | typeParams: [],
127 | },
128 | location: {
129 | filename: "https://deno.land/x/mod/mod.ts",
130 | line: 26,
131 | col: 2,
132 | },
133 | }, {
134 | kind: "method",
135 | name: "stringify",
136 | optional: true,
137 | isAbstract: false,
138 | isStatic: true,
139 | functionDef: {
140 | params: [{
141 | kind: "identifier",
142 | name: "value",
143 | optional: false,
144 | tsType: {
145 | kind: "keyword",
146 | keyword: "unknown",
147 | repr: "unknown",
148 | },
149 | }],
150 | isAsync: false,
151 | isGenerator: false,
152 | typeParams: [],
153 | },
154 | jsDoc: {
155 | doc: "some js doc for the method",
156 | tags: [{
157 | kind: "param",
158 | name: "value",
159 | doc: "the value to stringify",
160 | }],
161 | },
162 | location: {
163 | filename: "https://deno.land/x/mod/mod.ts",
164 | line: 27,
165 | col: 2,
166 | },
167 | }, {
168 | kind: "setter",
169 | name: "other",
170 | optional: false,
171 | isAbstract: false,
172 | isStatic: false,
173 | functionDef: {
174 | params: [{
175 | kind: "identifier",
176 | name: "value",
177 | optional: false,
178 | tsType: {
179 | kind: "keyword",
180 | keyword: "string",
181 | repr: "string",
182 | },
183 | }],
184 | isAsync: false,
185 | isGenerator: false,
186 | typeParams: [],
187 | },
188 | location: {
189 | filename: "https://deno.land/x/mod/mod.ts",
190 | line: 26,
191 | col: 2,
192 | },
193 | }, {
194 | kind: "getter",
195 | name: "other",
196 | optional: false,
197 | isAbstract: false,
198 | isStatic: false,
199 | functionDef: {
200 | params: [],
201 | returnType: {
202 | kind: "keyword",
203 | keyword: "string",
204 | repr: "string",
205 | },
206 | isAsync: false,
207 | isGenerator: false,
208 | typeParams: [],
209 | },
210 | location: {
211 | filename: "https://deno.land/x/mod/mod.ts",
212 | line: 26,
213 | col: 2,
214 | },
215 | }],
216 | extends: "Other",
217 | implements: [{
218 | kind: "typeRef",
219 | typeRef: { typeName: "AnInterface" },
220 | repr: "AnInterface",
221 | }, {
222 | kind: "typeRef",
223 | typeRef: { typeName: "OtherInterface" },
224 | repr: "OtherInterface",
225 | }],
226 | typeParams: [{
227 | name: "T",
228 | constraint: { kind: "keyword", keyword: "string", repr: "string" },
229 | }],
230 | superTypeParams: [{
231 | kind: "literal",
232 | literal: {
233 | kind: "string",
234 | string: "other",
235 | },
236 | repr: "string",
237 | }, {
238 | kind: "typeRef",
239 | typeRef: { typeName: "Value" },
240 | repr: "Value",
241 | }],
242 | decorators: [{
243 | name: "debug",
244 | args: ["arg"],
245 | location: {
246 | filename: "https://deno.land/x/mod/mod.ts",
247 | line: 22,
248 | col: 0,
249 | },
250 | }],
251 | },
252 | };
253 |
254 | export const enumNode: DocNodeEnum = {
255 | name: "SomeEnum",
256 | kind: "enum",
257 | location: {
258 | filename: "https://deno.land/x/mod/mod.ts",
259 | line: 100,
260 | col: 0,
261 | },
262 | declarationKind: "export",
263 | enumDef: {
264 | members: [{
265 | name: "String",
266 | init: {
267 | kind: "literal",
268 | literal: { kind: "string", string: "string" },
269 | repr: "string",
270 | },
271 | jsDoc: { doc: "Enum member with _JSDoc_." },
272 | location: {
273 | filename: "https://deno.land/x/mod/mod.ts",
274 | line: 101,
275 | col: 2,
276 | },
277 | }, {
278 | name: "Array",
279 | init: {
280 | kind: "literal",
281 | literal: { kind: "string", string: "array" },
282 | repr: "array",
283 | },
284 | location: {
285 | filename: "https://deno.land/x/mod/mod.ts",
286 | line: 102,
287 | col: 2,
288 | },
289 | }],
290 | },
291 | };
292 |
293 | export const interfaceNode: DocNodeInterface = {
294 | name: "AnInterface",
295 | kind: "interface",
296 | location: {
297 | filename: "https://deno.land/x/mod/mod.ts",
298 | line: 200,
299 | col: 0,
300 | },
301 | declarationKind: "export",
302 | interfaceDef: {
303 | extends: [{
304 | kind: "typeRef",
305 | typeRef: { typeName: "OtherInterface" },
306 | repr: "OtherInterface",
307 | }],
308 | methods: [{
309 | name: "a",
310 | kind: "getter",
311 | location: {
312 | filename: "https://deno.land/x/mod/mod.ts",
313 | line: 103,
314 | col: 2,
315 | },
316 | optional: false,
317 | params: [{
318 | kind: "identifier",
319 | name: "value",
320 | tsType: { kind: "keyword", keyword: "string", repr: "string" },
321 | optional: false,
322 | }],
323 | returnType: { kind: "keyword", keyword: "void", repr: "void" },
324 | typeParams: [],
325 | }, {
326 | name: "aMethod",
327 | kind: "method",
328 | location: {
329 | filename: "https://deno.land/x/mod/mod.ts",
330 | line: 101,
331 | col: 2,
332 | },
333 | jsDoc: {
334 | doc: "some markdown",
335 | tags: [{ kind: "deprecated", doc: "deprecated doc" }],
336 | },
337 | optional: true,
338 | params: [{
339 | kind: "identifier",
340 | name: "a",
341 | tsType: { kind: "keyword", keyword: "number", repr: "number" },
342 | optional: true,
343 | }],
344 | returnType: {
345 | kind: "typeRef",
346 | typeRef: {
347 | typeName: "Thingy",
348 | typeParams: [{
349 | kind: "typeRef",
350 | typeRef: { typeName: "T" },
351 | repr: "T",
352 | }],
353 | },
354 | repr: "Thingy",
355 | },
356 | typeParams: [],
357 | }],
358 | properties: [{
359 | name: "prop1",
360 | location: {
361 | filename: "https://deno.land/x/mod/mod.ts",
362 | line: 102,
363 | col: 2,
364 | },
365 | jsDoc: { doc: "Some prop JSDoc" },
366 | params: [],
367 | readonly: true,
368 | computed: false,
369 | optional: false,
370 | tsType: { kind: "keyword", keyword: "string", repr: "string" },
371 | typeParams: [],
372 | }],
373 | callSignatures: [{
374 | location: {
375 | filename: "https://deno.land/x/mod/mod.ts",
376 | line: 104,
377 | col: 2,
378 | },
379 | jsDoc: { doc: "some doc here", tags: [{ kind: "deprecated" }] },
380 | params: [{
381 | kind: "identifier",
382 | name: "a",
383 | optional: false,
384 | tsType: { kind: "typeRef", typeRef: { typeName: "T" }, repr: "T" },
385 | }],
386 | tsType: { kind: "keyword", keyword: "void", repr: "void" },
387 | typeParams: [{
388 | name: "U",
389 | constraint: { kind: "keyword", keyword: "string", repr: "string" },
390 | }],
391 | }],
392 | indexSignatures: [{
393 | readonly: false,
394 | params: [{ kind: "identifier", name: "property", optional: false }],
395 | tsType: { kind: "keyword", keyword: "string", repr: "string" },
396 | }],
397 | typeParams: [{
398 | name: "T",
399 | constraint: {
400 | kind: "keyword",
401 | keyword: "string",
402 | repr: "string",
403 | },
404 | }],
405 | },
406 | };
407 |
408 | export const fnNodes: DocNodeFunction[] = [{
409 | kind: "function",
410 | name: "a",
411 | location: { filename: "https://deno.land/x/mod/mod.ts", line: 300, col: 0 },
412 | declarationKind: "export",
413 | jsDoc: { doc: "overload **one**" },
414 | functionDef: {
415 | params: [{
416 | kind: "identifier",
417 | name: "b",
418 | optional: false,
419 | tsType: { kind: "keyword", keyword: "string", repr: "string" },
420 | }],
421 | returnType: { kind: "keyword", keyword: "string", repr: "string" },
422 | isAsync: false,
423 | isGenerator: false,
424 | typeParams: [],
425 | },
426 | }, {
427 | kind: "function",
428 | name: "a",
429 | location: { filename: "https://deno.land/x/mod/mod.ts", line: 300, col: 0 },
430 | declarationKind: "export",
431 | jsDoc: {
432 | doc: "overload **two**",
433 | tags: [
434 | { kind: "param", name: "b", doc: "a param doc" },
435 | { kind: "deprecated", doc: "don't use this!" },
436 | ],
437 | },
438 | functionDef: {
439 | params: [{
440 | kind: "identifier",
441 | name: "b",
442 | optional: false,
443 | tsType: { kind: "keyword", keyword: "number", repr: "number" },
444 | }],
445 | returnType: { kind: "keyword", keyword: "number", repr: "number" },
446 | isAsync: false,
447 | isGenerator: false,
448 | typeParams: [],
449 | },
450 | }];
451 |
452 | export const typeAliasNode: DocNodeTypeAlias = {
453 | kind: "typeAlias",
454 | name: "StringRecord",
455 | location: { filename: "https://deno.land/x/mod/mod.ts", line: 500, col: 0 },
456 | declarationKind: "export",
457 | jsDoc: {
458 | doc: "some sort of type alias",
459 | tags: [{ kind: "template", name: "V", doc: "the value of the record" }],
460 | },
461 | typeAliasDef: {
462 | tsType: {
463 | kind: "typeRef",
464 | typeRef: {
465 | typeName: "Record",
466 | typeParams: [
467 | { kind: "keyword", keyword: "string", repr: "string" },
468 | { kind: "typeRef", typeRef: { typeName: "V" }, repr: "V" },
469 | ],
470 | },
471 | repr: "",
472 | },
473 | typeParams: [{ name: "V" }],
474 | },
475 | };
476 |
477 | export const namespaceNode: DocNodeNamespace = {
478 | kind: "namespace",
479 | name: "things",
480 | location: { filename: "https://deno.land/x/mod/mod.ts", line: 400, col: 0 },
481 | declarationKind: "export",
482 | jsDoc: { doc: "some namespace level _doc_" },
483 | namespaceDef: {
484 | elements: [classNode, enumNode, interfaceNode, ...fnNodes, typeAliasNode],
485 | },
486 | };
487 |
--------------------------------------------------------------------------------
/doc/classes.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type ClassConstructorDef,
5 | type ClassMethodDef,
6 | type ClassPropertyDef,
7 | type DocNodeClass,
8 | } from "../deps.ts";
9 | import {
10 | DocEntry,
11 | Examples,
12 | getAccessibilityTag,
13 | nameToId,
14 | Section,
15 | Tag,
16 | tagVariants,
17 | } from "./doc_common.tsx";
18 | import { IndexSignaturesDoc } from "./interfaces.tsx";
19 | import { type Context } from "./markdown.tsx";
20 | import { Params } from "./params.tsx";
21 | import { services } from "../services.ts";
22 | import { style } from "../styles.ts";
23 | import { TypeDef, TypeParamsDoc } from "./types.tsx";
24 | import { assert, type Child, isDeprecated, take } from "./utils.ts";
25 | import { DocFunctionSummary } from "./functions.tsx";
26 |
27 | type ClassAccessorDef = ClassMethodDef & { kind: "getter" | "setter" };
28 | type ClassGetterDef = ClassMethodDef & { kind: "getter" };
29 | type ClassSetterDef = ClassMethodDef & { kind: "setter" };
30 | type ClassItemDef = ClassMethodDef | ClassPropertyDef;
31 |
32 | function compareAccessibility(
33 | a: ClassPropertyDef | ClassMethodDef,
34 | b: ClassPropertyDef | ClassMethodDef,
35 | ): number {
36 | if (a.accessibility !== b.accessibility) {
37 | if (a.accessibility === "private") {
38 | return -1;
39 | }
40 | if (b.accessibility === "private") {
41 | return 1;
42 | }
43 | if (a.accessibility === "protected") {
44 | return -1;
45 | }
46 | if (b.accessibility === "protected") {
47 | return 1;
48 | }
49 | }
50 | if (a.name === b.name && isClassAccessor(a) && isClassAccessor(b)) {
51 | return a.kind === "getter" ? -1 : 1;
52 | }
53 | if (a.name.startsWith("[") && b.name.startsWith("[")) {
54 | return a.name.localeCompare(b.name);
55 | }
56 | if (a.name.startsWith("[")) {
57 | return 1;
58 | }
59 | if (b.name.startsWith("[")) {
60 | return -1;
61 | }
62 | return a.name.localeCompare(b.name);
63 | }
64 |
65 | function getClassItems({ classDef: { properties, methods } }: DocNodeClass) {
66 | return [...properties, ...methods].sort((a, b) => {
67 | if (a.isStatic !== b.isStatic) {
68 | return a.isStatic ? 1 : -1;
69 | }
70 | if (
71 | (isClassProperty(a) && isClassProperty(b)) ||
72 | (isClassProperty(a) && isClassAccessor(b)) ||
73 | (isClassAccessor(a) && isClassProperty(b)) ||
74 | (isClassAccessor(a) && isClassAccessor(b)) ||
75 | (isClassMethod(a) && isClassMethod(b))
76 | ) {
77 | return compareAccessibility(a, b);
78 | }
79 | if (isClassAccessor(a) && !isClassAccessor(b)) {
80 | return -1;
81 | }
82 | if (isClassAccessor(b)) {
83 | return 1;
84 | }
85 | return isClassProperty(a) ? -1 : 1;
86 | });
87 | }
88 |
89 | function isClassAccessor(
90 | value: ClassPropertyDef | ClassMethodDef,
91 | ): value is ClassAccessorDef {
92 | return "kind" in value &&
93 | (value.kind === "getter" || value.kind === "setter");
94 | }
95 |
96 | function isClassGetter(
97 | value: ClassPropertyDef | ClassMethodDef,
98 | ): value is ClassGetterDef {
99 | return "kind" in value && value.kind === "getter";
100 | }
101 |
102 | function isClassMethod(
103 | value: ClassPropertyDef | ClassMethodDef,
104 | ): value is ClassMethodDef & { kind: "method" } {
105 | return "kind" in value && value.kind === "method";
106 | }
107 |
108 | function isClassProperty(
109 | value: ClassPropertyDef | ClassMethodDef,
110 | ): value is ClassPropertyDef {
111 | return "readonly" in value;
112 | }
113 |
114 | function isClassSetter(
115 | value: ClassPropertyDef | ClassMethodDef,
116 | ): value is ClassSetterDef {
117 | return "kind" in value && value.kind === "setter";
118 | }
119 |
120 | function ClassAccessorDoc(
121 | { get, set, context }: {
122 | get?: ClassGetterDef;
123 | set?: ClassSetterDef;
124 | context: Context;
125 | },
126 | ) {
127 | const name = (get ?? set)?.name;
128 | assert(name);
129 | const id = nameToId("accessor", name);
130 | const tsType = get?.functionDef.returnType ??
131 | set?.functionDef.params[0]?.tsType;
132 | const jsDoc = get?.jsDoc ?? set?.jsDoc;
133 | const location = get?.location ?? set?.location;
134 | assert(location);
135 | const accessibility = get?.accessibility ?? set?.accessibility;
136 | const isAbstract = get?.isAbstract ?? set?.isAbstract;
137 | const tags = [];
138 |
139 | const accessibilityTag = getAccessibilityTag(accessibility);
140 | if (accessibilityTag) {
141 | tags.push(accessibilityTag);
142 | }
143 |
144 | if (isAbstract) {
145 | tags.push(tagVariants.abstract());
146 | }
147 | if (isDeprecated(get ?? set)) {
148 | tags.push(tagVariants.deprecated());
149 | }
150 |
151 | if (get && !set) {
152 | tags.push(tagVariants.readonly());
153 | } else if (!get && set) {
154 | tags.push(tagVariants.writeonly());
155 | }
156 |
157 | return (
158 |
166 | {tsType && (
167 |
168 | :{" "}
169 |
170 |
171 | {tsType}
172 |
173 |
174 |
175 | )}
176 |
177 | );
178 | }
179 |
180 | function ClassMethodDoc(
181 | { children, className, context }: {
182 | children: Child;
183 | className: string;
184 | context: Context;
185 | },
186 | ) {
187 | const defs = take(children, true);
188 | const items = defs.map((
189 | {
190 | location,
191 | name,
192 | jsDoc,
193 | accessibility,
194 | optional,
195 | isAbstract,
196 | functionDef,
197 | isStatic,
198 | },
199 | i,
200 | ) => {
201 | const id = nameToId("method", `${defs[0].name}_${i}`);
202 |
203 | if (functionDef.hasBody && i !== 0) {
204 | return null;
205 | }
206 |
207 | const tags = [];
208 | const accessibilityTag = getAccessibilityTag(accessibility);
209 | if (accessibilityTag) {
210 | tags.push(accessibilityTag);
211 | }
212 |
213 | if (isAbstract) {
214 | tags.push(tagVariants.abstract());
215 | }
216 | if (optional) {
217 | tags.push(tagVariants.optional());
218 | }
219 | if (isDeprecated({ jsDoc })) {
220 | tags.push(tagVariants.deprecated());
221 | }
222 |
223 | return (
224 |
238 |
239 | {functionDef}
240 |
241 |
242 | );
243 | });
244 |
245 | return <>{items}>;
246 | }
247 |
248 | function ClassPropertyDoc(
249 | { children, context }: {
250 | children: Child;
251 | context: Context;
252 | },
253 | ) {
254 | const {
255 | location,
256 | name,
257 | tsType,
258 | jsDoc,
259 | accessibility,
260 | isAbstract,
261 | optional,
262 | readonly,
263 | } = take(children);
264 | const id = nameToId("prop", name);
265 |
266 | const tags = [];
267 | const accessibilityTag = getAccessibilityTag(accessibility);
268 | if (accessibilityTag) {
269 | tags.push(accessibilityTag);
270 | }
271 | if (isAbstract) {
272 | tags.push(tagVariants.abstract());
273 | }
274 | if (readonly) {
275 | tags.push(tagVariants.readonly());
276 | }
277 | if (optional) {
278 | tags.push(tagVariants.optional());
279 | }
280 | if (isDeprecated({ jsDoc })) {
281 | tags.push(tagVariants.deprecated());
282 | }
283 |
284 | return (
285 |
293 | {tsType && (
294 |
295 | :{" "}
296 |
297 | {tsType}
298 |
299 |
300 | )}
301 |
302 | );
303 | }
304 |
305 | function ClassItemsDoc(
306 | { children, className, context }: {
307 | children: Child;
308 | className: string;
309 | context: Context;
310 | },
311 | ) {
312 | const defs = take(children, true);
313 | if (!defs.length) {
314 | return null;
315 | }
316 |
317 | const properties: unknown[] = [];
318 | const methods: unknown[] = [];
319 | const staticProperties: unknown[] = [];
320 | const staticMethods: unknown[] = [];
321 |
322 | for (let i = 0; i < defs.length; i++) {
323 | const def = defs[i];
324 | if (isClassGetter(def)) {
325 | const next = defs[i + 1];
326 | if (next && isClassSetter(next) && def.name === next.name) {
327 | i++;
328 | (def.isStatic ? staticProperties : properties).push(
329 | ,
330 | );
331 | } else {
332 | (def.isStatic ? staticProperties : properties).push(
333 | ,
334 | );
335 | }
336 | } else if (isClassSetter(def)) {
337 | (def.isStatic ? staticProperties : properties).push(
338 | ,
339 | );
340 | } else if (isClassMethod(def)) {
341 | const methodList = [def];
342 | let next;
343 | while (
344 | (next = defs[i + 1]) && next && isClassMethod(next) &&
345 | def.name === next.name
346 | ) {
347 | i++;
348 | methodList.push(next);
349 | }
350 | (def.isStatic ? staticMethods : methods).push(
351 |
352 | {methodList}
353 | ,
354 | );
355 | } else {
356 | assert(isClassProperty(def));
357 | (def.isStatic ? staticProperties : properties).push(
358 |
359 | {def}
360 | ,
361 | );
362 | }
363 | }
364 |
365 | return (
366 | <>
367 | {properties.length !== 0 && (
368 | {properties}
369 | )}
370 | {methods.length !== 0 && {methods} }
371 | {staticProperties.length !== 0 && (
372 | {staticProperties}
373 | )}
374 | {staticMethods.length !== 0 && (
375 | {staticMethods}
376 | )}
377 | >
378 | );
379 | }
380 |
381 | function ConstructorsDoc(
382 | { children, name, context }: {
383 | children: Child;
384 | name: string;
385 | context: Context;
386 | },
387 | ) {
388 | const defs = take(children, true);
389 | if (!defs.length) {
390 | return null;
391 | }
392 | const items = defs.map(({ location, params, jsDoc, accessibility }, i) => {
393 | const id = nameToId("ctor", String(i));
394 | return (
395 | new,
400 | getAccessibilityTag(accessibility),
401 | ]}
402 | name={name}
403 | jsDoc={jsDoc}
404 | context={context}
405 | >
406 | (
407 | {params}
408 | )
409 |
410 | );
411 | });
412 |
413 | return {items} ;
414 | }
415 |
416 | export function DocSubTitleClass(
417 | { children, context }: { children: Child; context: Context },
418 | ) {
419 | const { classDef } = take(children);
420 |
421 | const extendsHref = classDef.extends
422 | ? services.lookupHref(context.url, context.namespace, classDef.extends)
423 | : undefined;
424 |
425 | return (
426 | <>
427 | {classDef.implements.length !== 0 && (
428 |
429 | {" implements "}
430 | {classDef.implements.map((typeDef, i) => (
431 | <>
432 |
433 | {typeDef}
434 |
435 | {i !== (classDef.implements.length - 1) && ,{" "}}
436 | >
437 | ))}
438 |
439 | )}
440 |
441 | {classDef.extends && (
442 |
443 | {" extends "}
444 | {extendsHref
445 | ? {classDef.extends}
446 | : {classDef.extends}}
447 |
448 | {classDef.superTypeParams.length !== 0 && (
449 |
450 | {"<"}
451 | {classDef.superTypeParams.map((typeDef, i) => (
452 | <>
453 |
454 | {typeDef}
455 |
456 | {i !== (classDef.superTypeParams.length - 1) && (
457 | ,{" "}
458 | )}
459 | >
460 | ))}
461 | {">"}
462 |
463 | )}
464 |
465 |
466 | )}
467 | >
468 | );
469 | }
470 |
471 | export function DocBlockClass(
472 | { children, context }: { children: Child; context: Context },
473 | ) {
474 | const def = take(children);
475 | context.typeParams = def.classDef.typeParams.map(({ name }) => name);
476 | const classItems = getClassItems(def);
477 | return (
478 |
479 | {def.jsDoc}
480 |
481 |
482 | {def.classDef.constructors}
483 |
484 |
485 |
486 | {def.classDef.typeParams}
487 |
488 |
489 |
490 | {def.classDef.indexSignatures}
491 |
492 |
493 |
494 | {classItems}
495 |
496 |
497 | );
498 | }
499 |
--------------------------------------------------------------------------------
/doc/types.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | htmlEntities,
6 | type JsDocTagNamed,
7 | type LiteralCallSignatureDef,
8 | type LiteralIndexSignatureDef,
9 | type LiteralMethodDef,
10 | type LiteralPropertyDef,
11 | type Location,
12 | type TruePlusMinus,
13 | type TsTypeDef,
14 | type TsTypeIntersectionDef,
15 | type TsTypeMappedDef,
16 | type TsTypeParamDef,
17 | type TsTypeTupleDef,
18 | type TsTypeUnionDef,
19 | } from "../deps.ts";
20 | import { Params } from "./params.tsx";
21 | import { services } from "../services.ts";
22 | import { style } from "../styles.ts";
23 | import { type Child, maybe, take } from "./utils.ts";
24 | import { Context } from "./markdown.tsx";
25 | import { DocEntry, nameToId, Section, tagVariants } from "./doc_common.tsx";
26 |
27 | function LiteralIndexSignatures(
28 | { children, context }: {
29 | children: Child;
30 | context: Context;
31 | },
32 | ) {
33 | const signatures = take(children, true);
34 | if (!signatures.length) {
35 | return null;
36 | }
37 | const items = signatures.map(({ params, readonly, tsType }) => (
38 | <>
39 | {maybe(
40 | readonly,
41 |
42 | readonly{" "}
43 | ,
44 | )}[
45 | {params}
46 | ]{tsType &&
47 | (
48 | <>
49 | :{" "}
50 |
51 | {tsType}
52 |
53 | >
54 | )};{" "}
55 | >
56 | ));
57 |
58 | return <>{items}>;
59 | }
60 |
61 | function LiteralCallSignatures({ children, context }: {
62 | children: Child;
63 | context: Context;
64 | }) {
65 | const signatures = take(children, true);
66 | if (!signatures.length) {
67 | return null;
68 | }
69 | const items = signatures.map(({ typeParams, params, tsType }) => (
70 | <>
71 |
72 | {typeParams}
73 | (
74 | {params}
75 | ){tsType && (
76 | <>
77 | :{" "}
78 |
79 | {tsType}
80 |
81 | >
82 | )};{" "}
83 | >
84 | ));
85 | return <>{items}>;
86 | }
87 |
88 | function LiteralProperties(
89 | { children, context }: {
90 | children: Child;
91 | context: Context;
92 | },
93 | ) {
94 | const properties = take(children, true);
95 | if (!properties.length) {
96 | return null;
97 | }
98 | const items = properties.map(
99 | ({ name, readonly, computed, optional, tsType }) => (
100 | <>
101 | {maybe(
102 | readonly,
103 |
104 | readonly{" "}
105 | ,
106 | )}
107 | {maybe(computed, `[${name}]`, name)}
108 | {maybe(optional, "?")}
109 | {tsType && (
110 | <>
111 | :{" "}
112 |
113 | {tsType}
114 |
115 | >
116 | )}
117 | {"; "}
118 | >
119 | ),
120 | );
121 | return <>{items}>;
122 | }
123 |
124 | function LiteralMethods({ children, context }: {
125 | children: Child;
126 | context: Context;
127 | }) {
128 | const methods = take(children, true);
129 | if (!methods.length) {
130 | return null;
131 | }
132 | const items = methods.map(
133 | (
134 | {
135 | name,
136 | kind,
137 | optional,
138 | computed,
139 | returnType,
140 | typeParams,
141 | params,
142 | },
143 | ) => (
144 | <>
145 | {kind === "getter"
146 | ? get{" "}
147 | : kind === "setter"
148 | ? set{" "}
149 | : undefined}
150 | {name === "new"
151 | ? {name}{" "}
152 | : computed
153 | ? `[${name}]`
154 | : name}
155 | {maybe(optional, "?")}
156 |
157 | {typeParams}
158 | (
159 | {params}
160 | ){returnType && (
161 | <>
162 | :{" "}
163 |
164 | {returnType}
165 |
166 | >
167 | )}
168 | {"; "}
169 | >
170 | ),
171 | );
172 | return <>{items}>;
173 | }
174 |
175 | function MappedOptional(
176 | { children }: { children: Child },
177 | ) {
178 | const optional = take(children);
179 | switch (optional) {
180 | case true:
181 | return <>?>;
182 | case "+":
183 | return <>+?>;
184 | case "-":
185 | return <>-?>;
186 | default:
187 | return null;
188 | }
189 | }
190 |
191 | function MappedReadOnly(
192 | { children }: {
193 | children: Child;
194 | },
195 | ) {
196 | const readonly = take(children);
197 | switch (readonly) {
198 | case true:
199 | return readonly{" "};
200 | case "+":
201 | return +readonly{" "};
202 | case "-":
203 | return -readonly{" "};
204 | default:
205 | return null;
206 | }
207 | }
208 |
209 | export function TypeArguments(
210 | { children, context }: {
211 | children: Child;
212 | context: Context;
213 | },
214 | ) {
215 | const args = take(children, true);
216 | if (!args || !args.length || !args[0]) {
217 | return null;
218 | }
219 | const items = [];
220 | for (let i = 0; i < args.length; i++) {
221 | items.push(
222 |
223 | {args[i]}
224 | ,
225 | );
226 | if (i < args.length - 1) {
227 | items.push(<>{", "}>);
228 | }
229 | }
230 | return <><{items}>>;
231 | }
232 |
233 | export function TypeDef({ children, context }: {
234 | children: Child;
235 | context: Context;
236 | }) {
237 | const def = take(children);
238 | switch (def.kind) {
239 | case "array":
240 | return (
241 | <>
242 |
243 | {def.array}
244 | []
245 | >
246 | );
247 | case "conditional": {
248 | const {
249 | conditionalType: { checkType, extendsType, trueType, falseType },
250 | } = def;
251 | return (
252 | <>
253 |
254 | {checkType}
255 | {" "}
256 | extends{" "}
257 |
258 | {extendsType}
259 | {" "}
260 | ?{" "}
261 |
262 | {trueType}
263 | {" "}
264 | :{" "}
265 |
266 | {falseType}
267 |
268 | >
269 | );
270 | }
271 | case "fnOrConstructor": {
272 | const { fnOrConstructor } = def;
273 | return (
274 | <>
275 | {maybe(fnOrConstructor.constructor, new{" "})}
276 |
279 | {fnOrConstructor.typeParams}
280 | (
281 | {fnOrConstructor.params}
282 | ) =>{" "}
283 |
284 | {fnOrConstructor.tsType}
285 |
286 | >
287 | );
288 | }
289 | case "importType": {
290 | const { importType } = def;
291 | return (
292 | <>
293 | import("{importType.specifier}"){importType.qualifier &&
294 | .{importType.qualifier}}
295 |
296 | {importType.typeParams}
297 |
298 | >
299 | );
300 | }
301 | case "indexedAccess": {
302 | const { indexedAccess: { objType, indexType } } = def;
303 | return (
304 | <>
305 |
306 | {objType}
307 | [
308 | {indexType}
309 | ]
310 | >
311 | );
312 | }
313 | case "infer": {
314 | const { infer: { typeParam } } = def;
315 | return (
316 | <>
317 | infer{" "}
318 |
319 | {typeParam}
320 |
321 | >
322 | );
323 | }
324 | case "intersection":
325 | return (
326 |
327 | {def}
328 |
329 | );
330 | case "keyword": {
331 | return {def.keyword};
332 | }
333 | case "literal": {
334 | const { literal: { kind }, repr } = def;
335 | let item;
336 | switch (kind) {
337 | case "bigInt":
338 | case "boolean":
339 | case "number":
340 | item = {repr};
341 | break;
342 | case "string":
343 | item = {JSON.stringify(repr)};
344 | break;
345 | case "template":
346 | // TODO(@kitsonk) do this properly and escape properly
347 | item = `{repr}`;
348 | break;
349 | }
350 | return <>{item}>;
351 | }
352 | case "mapped":
353 | return (
354 |
355 | {def}
356 |
357 | );
358 | case "optional": {
359 | const { optional } = def;
360 | return (
361 |
362 | {optional}
363 |
364 | );
365 | }
366 | case "parenthesized": {
367 | const { parenthesized } = def;
368 | return (
369 | <>
370 | (
371 | {parenthesized}
372 | )
373 | >
374 | );
375 | }
376 | case "rest": {
377 | const { rest } = def;
378 | return (
379 | <>
380 | ...
381 | {rest}
382 |
383 | >
384 | );
385 | }
386 | case "this": {
387 | return this;
388 | }
389 | case "tuple": {
390 | return (
391 |
392 | {def}
393 |
394 | );
395 | }
396 | case "typeLiteral": {
397 | const {
398 | typeLiteral: { indexSignatures, callSignatures, properties, methods },
399 | } = def;
400 | return (
401 | <>
402 | {"{ "}
403 |
404 | {indexSignatures}
405 |
406 |
407 | {callSignatures}
408 |
409 |
410 | {properties}
411 |
412 |
413 | {methods}
414 |
415 | {" }"}
416 | >
417 | );
418 | }
419 | case "typeOperator": {
420 | const { typeOperator: { operator, tsType } } = def;
421 | return (
422 | <>
423 | {operator}{" "}
424 |
425 | {tsType}
426 |
427 | >
428 | );
429 | }
430 | case "typePredicate": {
431 | const {
432 | typePredicate: { asserts, param: { type: paramType, name }, type },
433 | } = def;
434 | return (
435 | <>
436 | {maybe(asserts, asserts{" "})}
437 | {maybe(paramType === "this", this, name)}
438 | {type && (
439 | <>
440 | {" is "}
441 |
442 | {type}
443 |
444 | >
445 | )}
446 | >
447 | );
448 | }
449 | case "typeQuery": {
450 | const { typeQuery } = def;
451 | return <>{typeQuery}>;
452 | }
453 | case "typeRef": {
454 | const { typeRef } = def;
455 |
456 | let href;
457 | if (context.typeParams?.includes(typeRef.typeName)) {
458 | const url = new URL(context.url);
459 | url.hash = nameToId("type_param", typeRef.typeName);
460 | href = url.href;
461 | } else {
462 | href = services.lookupHref(
463 | context.url,
464 | context.namespace,
465 | typeRef.typeName,
466 | );
467 | }
468 | return (
469 | <>
470 | {href
471 | ? {typeRef.typeName}
472 | : {typeRef.typeName}}
473 |
474 | {typeRef.typeParams}
475 |
476 | >
477 | );
478 | }
479 | case "union":
480 | return (
481 |
482 | {def}
483 |
484 | );
485 | default:
486 | return (
487 | <>
488 | {htmlEntities.encode((def as TsTypeDef).repr)}
489 | >
490 | );
491 | }
492 | }
493 |
494 | function TypeDefIntersection(
495 | { children, context }: {
496 | children: Child;
497 | context: Context;
498 | },
499 | ) {
500 | const { intersection } = take(children);
501 | const lastIndex = intersection.length - 1;
502 | if (intersection.length <= 3) {
503 | const items = [];
504 | for (let i = 0; i < intersection.length; i++) {
505 | items.push(
506 |
507 | {intersection[i]}
508 | ,
509 | );
510 | if (i < lastIndex) {
511 | items.push({" & "});
512 | }
513 | }
514 | return <>{items}>;
515 | }
516 | const items = intersection.map((def) => (
517 |
518 | {" & "}
519 |
520 | {def}
521 |
522 |
523 | ));
524 | return {items};
525 | }
526 |
527 | function TypeDefMapped(
528 | { children, context }: {
529 | children: Child;
530 | context: Context;
531 | },
532 | ) {
533 | const {
534 | mappedType: { readonly, typeParam, nameType, optional, tsType },
535 | } = take(children);
536 | return (
537 | <>
538 | {readonly} [
542 | {typeParam}
543 |
544 | {nameType && (
545 | <>
546 |
547 | in keyof{" "}
548 |
549 |
550 | {nameType}
551 |
552 | >
553 | )}]{optional}
554 | {tsType && (
555 | <>
556 | :{" "}
557 |
558 | {tsType}
559 |
560 | >
561 | )}
562 | >
563 | );
564 | }
565 |
566 | function TypeDefTuple(
567 | { children, context }: {
568 | children: Child;
569 | context: Context;
570 | },
571 | ) {
572 | const { tuple } = take(children);
573 | if (tuple.length <= 3) {
574 | const items = [];
575 | for (let i = 0; i < tuple.length; i++) {
576 | items.push(
577 |
578 | {tuple[i]}
579 | ,
580 | );
581 | if (i < tuple.length - 1) {
582 | items.push(", ");
583 | }
584 | }
585 | return [{items}];
586 | }
587 | const items = tuple.map((def) => (
588 |
589 |
590 | {def}
591 | ,{" "}
592 |
593 | ));
594 | return [{items}];
595 | }
596 |
597 | function TypeDefUnion(
598 | { children, context }: {
599 | children: Child;
600 | context: Context;
601 | },
602 | ) {
603 | const { union } = take(children);
604 | const lastIndex = union.length - 1;
605 | if (union.length <= 3) {
606 | const items = [];
607 | for (let i = 0; i < union.length; i++) {
608 | items.push(
609 |
610 | {union[i]}
611 | ,
612 | );
613 | if (i < lastIndex) {
614 | items.push({" | "});
615 | }
616 | }
617 | return {items};
618 | }
619 | const items = union.map((def) => (
620 |
621 | {" | "}
622 |
623 | {def}
624 |
625 |
626 | ));
627 | return {items};
628 | }
629 |
630 | function TypeParamSummary(
631 | { children, constraintKind = "extends", context }: {
632 | children: Child;
633 | constraintKind?: string;
634 | context: Context;
635 | },
636 | ) {
637 | const { name, constraint, default: def } = take(children);
638 | return (
639 | <>
640 | {name}
641 | {constraint && (
642 | <>
643 | {` ${constraintKind} `}
644 |
645 | {constraint}
646 |
647 | >
648 | )}
649 | {def && (
650 | <>
651 | {` = `}
652 |
653 | {def}
654 |
655 | >
656 | )}
657 | >
658 | );
659 | }
660 |
661 | export function DocTypeParamsSummary(
662 | { children, context }: {
663 | children: Child;
664 | context: Context;
665 | },
666 | ) {
667 | const typeParams = take(children, true);
668 | if (typeParams.length === 0) {
669 | return null;
670 | }
671 |
672 | return (
673 |
674 | {"<"}
675 | {typeParams.map((typeParam, i) => (
676 | <>
677 |
678 | {typeParam.name}
679 | {typeParam.constraint && (
680 |
681 | {" extends "}
682 |
683 | {typeParam.constraint}
684 |
685 |
686 | )}
687 | {typeParam.default && (
688 |
689 | {" = "}
690 |
691 | {typeParam.default}
692 |
693 |
694 | )}
695 |
696 | {i !== (typeParams.length - 1) && ,{" "}}
697 | >
698 | ))}
699 | {">"}
700 |
701 | );
702 | }
703 |
704 | export function TypeParam(
705 | { children, id, location, doc, context }: {
706 | children: Child;
707 | id: string;
708 | location: Location;
709 | doc?: JsDocTagNamed;
710 | context: Context;
711 | },
712 | ) {
713 | const def = take(children);
714 |
715 | const tags = [];
716 | if (def.default) {
717 | tags.push(tagVariants.optional());
718 | }
719 |
720 | return (
721 |
729 | {def.constraint && (
730 |
731 | {" extends "}
732 |
733 | {def.constraint}
734 |
735 |
736 | )}
737 | {def.default && (
738 |
739 | {" = "}
740 |
741 | {def.default}
742 |
743 |
744 | )}
745 |
746 | );
747 | }
748 |
749 | export function TypeParamsDoc(
750 | { children, base, context }: {
751 | children: Child;
752 | base: DocNode;
753 | context: Context;
754 | },
755 | ) {
756 | const defs = take(children, true);
757 |
758 | const typeParamDocs: JsDocTagNamed[] =
759 | (base.jsDoc?.tags?.filter(({ kind }) => kind === "template") as
760 | | JsDocTagNamed[]
761 | | undefined) ??
762 | [];
763 |
764 | const items = defs.map((typeParam) => {
765 | const id = nameToId("type_param", typeParam.name);
766 |
767 | return (
768 | name === typeParam.name)}
772 | context={context}
773 | >
774 | {typeParam}
775 |
776 | );
777 | });
778 |
779 | return {items} ;
780 | }
781 |
--------------------------------------------------------------------------------
/icons.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | export function Dir(props: { class?: string }) {
4 | return (
5 |
18 | );
19 | }
20 |
21 | export function Source(props: { class?: string }) {
22 | return (
23 |
46 | );
47 | }
48 |
49 | export function Index() {
50 | return (
51 |
65 | );
66 | }
67 |
68 | export function TriangleRight(
69 | props: {
70 | class?: string;
71 | tabindex?: number;
72 | onKeyDown?: string;
73 | "aria-label"?: string;
74 | },
75 | ) {
76 | return (
77 | // @ts-ignore onKeyDown does support strings
78 |
88 | );
89 | }
90 |
91 | export function Copy() {
92 | return (
93 |
105 | );
106 | }
107 |
108 | export function GitHub(props: { class?: string }) {
109 | return (
110 |
121 | );
122 | }
123 |
124 | export function Discord(props: { class?: string }) {
125 | return (
126 |
133 | );
134 | }
135 |
136 | export function Twitter(props: { class?: string }) {
137 | return (
138 |
145 | );
146 | }
147 |
148 | export function Plus() {
149 | return (
150 |
176 | );
177 | }
178 |
179 | export function Minus(props: { class?: string }) {
180 | return (
181 |
199 | );
200 | }
201 |
202 | export function TrashCan(props: { class?: string }) {
203 | return (
204 |
219 | );
220 | }
221 |
222 | export function ExclamationMark(props: { class?: string }) {
223 | return (
224 |
250 | );
251 | }
252 |
253 | export function Logo(props: { class?: string }) { // Size not normalized
254 | return (
255 |
294 | );
295 | }
296 |
297 | export function Deno(props: { class?: string }) { // Size not normalized
298 | return (
299 |
330 | );
331 | }
332 |
333 | export function Link(props: { class?: string }) {
334 | return (
335 |
357 | );
358 | }
359 |
360 | export function LinkLine(props: { class?: string }) {
361 | return (
362 |
385 | );
386 | }
387 |
388 | export function YouTube(props: { class?: string }) {
389 | return (
390 |
404 | );
405 | }
406 |
407 | export function Mastodon(props: { class?: string }) {
408 | return (
409 |
424 | );
425 | }
426 |
427 | export function Menu(props: { class?: string }) { // Size not normalized
428 | return (
429 |
462 | );
463 | }
464 |
465 | export function Cross(props: { class?: string }) { // Size not normalized
466 | return (
467 |
480 | );
481 | }
482 |
--------------------------------------------------------------------------------