├── .github └── workflows │ └── ci.yml ├── LICENSE.md ├── README.md ├── _showcase ├── components │ ├── ComponentLink.tsx │ ├── ComponentTitle.tsx │ ├── Showcase.tsx │ ├── ShowcaseDocBlocks.tsx │ └── ShowcaseModuleDoc.tsx ├── data.ts ├── deno.json ├── dev.ts ├── fresh.gen.ts ├── import_map.json ├── main.ts ├── main.tsx ├── routes │ ├── _app.tsx │ ├── docblocks.tsx │ ├── index.tsx │ └── moduledoc.tsx ├── static │ ├── favicon.ico │ └── logo.svg ├── twind.config.ts └── util.ts ├── deno.json ├── deps.ts ├── deps_test.ts ├── doc ├── classes.tsx ├── doc.ts ├── doc_block.tsx ├── doc_common.tsx ├── doc_title.tsx ├── enums.tsx ├── functions.tsx ├── interfaces.tsx ├── library_doc.tsx ├── library_doc_panel.tsx ├── markdown.tsx ├── module_doc.tsx ├── module_index.tsx ├── module_index_panel.tsx ├── namespaces.tsx ├── params.tsx ├── symbol_doc.tsx ├── symbol_kind.tsx ├── type_aliases.tsx ├── types.tsx ├── usage.tsx ├── utils.ts └── variables.tsx ├── doc_test.ts ├── icons.tsx ├── services.ts ├── styles.ts └── twind.config.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | deno: 7 | name: doc-components-${{ matrix.os }} 8 | if: | 9 | github.event_name == 'push' || 10 | !startsWith(github.event.pull_request.head.label, 'denoland:') 11 | runs-on: ${{ matrix.os }} 12 | timeout-minutes: 30 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | 17 | env: 18 | GH_ACTIONS: 1 19 | 20 | steps: 21 | - name: ☑️ clone repository 22 | uses: actions/checkout@v2 23 | 24 | - name: ➡️ install Deno 25 | uses: denoland/setup-deno@v1 26 | with: 27 | deno-version: 1.x 28 | 29 | - name: 💄 format 30 | run: deno fmt 31 | 32 | - name: 💄 lint 33 | run: deno lint 34 | 35 | - name: 🧪 test 36 | run: deno task test 37 | 38 | - name: 🔎 check 39 | run: deno task check 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2021-2023 the Deno authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This repo has been archived, please use the `deno doc --html` command or the `deno_doc` rust crate. 3 | 4 | # deno_doc_components 5 | 6 | [![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https://deno.land/x/deno_doc_components/mod.ts) 7 | [![Build Status - Cirrus][]][Build status] [![Twitter handle][]][Twitter badge] 8 | [![Discord Chat](https://img.shields.io/discord/684898665143206084?logo=discord&style=social)](https://discord.gg/deno) 9 | 10 | A set of components used to render [deno_doc](https://deno.land/x/deno_doc) 11 | [DocNodes](https://doc.deno.land/https://deno.land/x/deno_doc/lib/types.d.ts/~/DocNode). 12 | 13 | ## Showcase 14 | 15 | The repository contains a showcase application to see example rendering of the 16 | components in the `_showcase` directory. It can be run locally using: 17 | 18 | ``` 19 | > deno task showcase 20 | ``` 21 | 22 | It is also available on 23 | [deno-doc-components.deno.dev](https://deno-doc-components.deno.dev/). 24 | 25 | ## Usage 26 | 27 | ### Services 28 | 29 | There are other services that may need to be provided to integrate the 30 | components into an application. These can also be provided via `setup()` and 31 | will override the default behavior. 32 | 33 | #### `href()` 34 | 35 | The function `href()` should return a link string value which will be used at 36 | points in rendering when linking off to other modules and symbols. It has the 37 | following signature: 38 | 39 | ```ts 40 | interface Configuration { 41 | href?: (path: string, symbol?: string) => string; 42 | } 43 | ``` 44 | 45 | The `path` will be set to the module being requested (e.g. `/mod.ts`) and 46 | optionally a `symbol` will be provided, if targeting a specific exported symbol 47 | from that module. 48 | 49 | #### `lookupSymbolHref()` 50 | 51 | The function `lookupSymbolHref()` is used when the components are trying to 52 | resolve a link to a specific symbol. An implementation should attempt to resolve 53 | the symbol from the current namespace to the current module, to the global 54 | namespace, returning a link to the first matching symbol it finds. If the symbol 55 | cannot be found, `undefined` should be returned. 56 | 57 | ```ts 58 | interface Configuration { 59 | lookupSymbolHref?: ( 60 | current: string, 61 | namespace: string | undefined, 62 | symbol: string, 63 | ) => string | undefined; 64 | } 65 | ``` 66 | 67 | The `current` will be set to the module that is the current reference point 68 | (e.g. `/mod.ts`), any `namespace` that is in the current scope (e.g. `errors`) 69 | and the symbol that is being looked for (e.g. `Uint8Array`). If the current 70 | namespace is with another namespace, they will be separated by a `.` (e.g. 71 | `custom.errors`). 72 | 73 | #### twind 74 | 75 | twind has some shared hidden state, which means that doc_components needs to 76 | share the same versions of the remote twind modules. In order to do that, 77 | `doc_components` from the bare specifiers `twind`, `twind/colors`, `twind/css`. 78 | Also, if you are setting up twind to SSR in a client application, you will end 79 | up needing items from `twind/sheets`. 80 | 81 | Therefore it is expected that you will use an 82 | [import map](https://deno.land/manual/node/import_maps) to provide the specific 83 | version of twind you are using in your end application. This repository contains 84 | an example which is used for the showcase. 85 | 86 | You can specify a twind setup configuration by passing a property of `tw` when 87 | performing a setup. For example: 88 | 89 | ```ts 90 | import { setup } from "https://deno.land/x/deno_doc_components/services.ts"; 91 | 92 | await setup({ 93 | tw: { 94 | theme: { 95 | colors: { 96 | transparent: "transparent", 97 | }, 98 | }, 99 | }, 100 | }); 101 | ``` 102 | 103 | The `/services.ts` module exports a `theme` object which is the default theme 104 | settings that the `doc_components` use with twind, which can be used when you 105 | are performing setup from twind yourself: 106 | 107 | ```ts 108 | import { setup } from "twind"; 109 | import { theme } from "https://deno.land/x/deno_doc_components/services.ts"; 110 | 111 | setup({ theme }); 112 | ``` 113 | 114 | --- 115 | 116 | Copyright 2021-2023 the Deno authors. All rights reserved. MIT License. 117 | 118 | [Build Status - Cirrus]: https://github.com/denoland/doc_components/workflows/ci/badge.svg?branch=main&event=push 119 | [Build status]: https://github.com/denoland/doc_components/actions 120 | [Twitter badge]: https://twitter.com/intent/follow?screen_name=deno_land 121 | [Twitter handle]: https://img.shields.io/twitter/follow/deno_land.svg?style=social&label=Follow 122 | -------------------------------------------------------------------------------- /_showcase/components/ComponentLink.tsx: -------------------------------------------------------------------------------- 1 | import { headerify } from "../util.ts"; 2 | 3 | export function ComponentLink({ children: name }: { children: string }) { 4 | return ( 5 |
  • 6 | 10 | {name} 11 | 12 |
  • 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /_showcase/components/ComponentTitle.tsx: -------------------------------------------------------------------------------- 1 | import { headerify } from "../util.ts"; 2 | 3 | export function ComponentTitle( 4 | { children: name, module }: { children: string; module: string }, 5 | ) { 6 | const href = `https://github.com/denoland/doc_components/blob/main${module}`; 7 | return ( 8 |

    12 | 17 | {name} 18 | 19 |

    20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /_showcase/components/Showcase.tsx: -------------------------------------------------------------------------------- 1 | import type { DocPageIndex, DocPageModule, DocPageSymbol } from "apiland/types"; 2 | import { apply, tw } from "twind"; 3 | import { css } from "twind/css"; 4 | 5 | import { tagVariants } from "@doc_components/doc/doc_common.tsx"; 6 | import { Markdown } from "@doc_components/doc/markdown.tsx"; 7 | import { ModuleDoc } from "@doc_components/doc/module_doc.tsx"; 8 | import { ModuleIndex } from "@doc_components/doc/module_index.tsx"; 9 | import { SymbolDoc } from "@doc_components/doc/symbol_doc.tsx"; 10 | import { Usage } from "@doc_components/doc/usage.tsx"; 11 | 12 | import { ComponentLink } from "./ComponentLink.tsx"; 13 | import { ComponentTitle } from "./ComponentTitle.tsx"; 14 | 15 | const app = css({ 16 | ":global": { 17 | "html": apply`bg-white dark:bg-gray-900`, 18 | }, 19 | }); 20 | 21 | export function Showcase( 22 | { url, modulePage, symbolPage, indexPage }: { 23 | url: URL; 24 | modulePage: DocPageModule; 25 | symbolPage: DocPageSymbol; 26 | indexPage: DocPageIndex; 27 | }, 28 | ) { 29 | return ( 30 |
    33 |

    Deno Doc Components

    34 |

    Component Showcase

    35 | 56 | 57 | 58 | Markdown Summary 59 | 60 | 61 | {`Some _markdown_ with [links](https://deno.land/) and symbol links, like: {@linkcode Router}`} 62 | 63 | 64 | 65 | ModuleIndex 66 | 67 | 68 | {indexPage.items} 69 | 70 | 71 | ModuleDoc 72 | 73 | {modulePage.docNodes} 74 | 75 | 76 | SymbolDoc 77 | 78 | {symbolPage.docNodes} 79 | 80 | 81 | Tags 82 |
    83 | {Object.values(tagVariants).map((tag) => tag())} 84 |
    85 | 86 | Usage 87 |
    88 | 89 | 93 | 97 | 102 |
    103 |
    104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /_showcase/components/ShowcaseDocBlocks.tsx: -------------------------------------------------------------------------------- 1 | import { apply, tw } from "twind"; 2 | import { css } from "twind/css"; 3 | import { 4 | type DocNode, 5 | type DocNodeClass, 6 | type DocNodeEnum, 7 | type DocNodeFunction, 8 | type DocNodeInterface, 9 | type DocNodeNamespace, 10 | type DocNodeTypeAlias, 11 | } from "@doc_components/deps.ts"; 12 | import { DocBlockClass } from "@doc_components/doc/classes.tsx"; 13 | import { DocBlockEnum } from "@doc_components/doc/enums.tsx"; 14 | import { DocBlockFunction } from "@doc_components/doc/functions.tsx"; 15 | import { DocBlockInterface } from "@doc_components/doc/interfaces.tsx"; 16 | import { DocBlockNamespace } from "@doc_components/doc/namespaces.tsx"; 17 | import { DocBlockTypeAlias } from "@doc_components/doc/type_aliases.tsx"; 18 | 19 | import { ComponentLink } from "./ComponentLink.tsx"; 20 | import { ComponentTitle } from "./ComponentTitle.tsx"; 21 | 22 | const app = css({ 23 | ":global": { 24 | "html": apply`bg-white dark:bg-gray-900`, 25 | }, 26 | }); 27 | 28 | export function ShowcaseDocBlocks( 29 | { children: docNodes, url }: { children: DocNode[]; url: URL }, 30 | ) { 31 | const classNode = docNodes.find(({ kind }) => 32 | kind === "class" 33 | ) as DocNodeClass; 34 | const enumNode = docNodes.find(({ kind }) => kind === "enum") as DocNodeEnum; 35 | const interfaceNode = docNodes.find(({ kind }) => 36 | kind === "interface" 37 | ) as DocNodeInterface; 38 | const fnNodes = docNodes.filter(({ kind }) => 39 | kind === "function" 40 | ) as DocNodeFunction[]; 41 | const typeAliasNode = docNodes.find(({ kind }) => 42 | kind === "typeAlias" 43 | ) as DocNodeTypeAlias; 44 | const namespaceNode = docNodes.find(({ kind }) => 45 | kind === "namespace" 46 | ) as DocNodeNamespace; 47 | return ( 48 |
    51 |

    Deno Doc Components

    52 |

    DocBlock Component Showcase

    53 | 64 | 65 | DocBlockClass 66 | {classNode} 67 | 68 | DocBlockEnum 69 | {enumNode} 70 | 71 | 72 | DocBlockInterface 73 | 74 | {interfaceNode} 75 | 76 | DocBlockFn 77 | {fnNodes} 78 | 79 | 80 | DocNodeTypeAlias 81 | 82 | {typeAliasNode} 83 | 84 | 85 | DocBlockNamespace 86 | 87 | {namespaceNode} 88 |
    89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /_showcase/components/ShowcaseModuleDoc.tsx: -------------------------------------------------------------------------------- 1 | import { apply, tw } from "twind"; 2 | import { css } from "twind/css"; 3 | import { type DocNode } from "@doc_components/deps.ts"; 4 | 5 | import { ModuleDoc } from "@doc_components/doc/module_doc.tsx"; 6 | 7 | import { ComponentLink } from "./ComponentLink.tsx"; 8 | import { ComponentTitle } from "./ComponentTitle.tsx"; 9 | 10 | const app = css({ 11 | ":global": { 12 | "html": apply`bg-white dark:bg-gray-900`, 13 | }, 14 | }); 15 | 16 | export function ShowcaseModuleDoc({ url }: { url: URL }) { 17 | const noExportsDocNodes: DocNode[] = [{ 18 | kind: "import", 19 | declarationKind: "private", 20 | importDef: { src: "https://deno.land/x/std/testing/asserts.ts" }, 21 | name: "assert", 22 | location: { 23 | filename: "https://deno.land/x/oak@v11.1.0/mod.ts", 24 | col: 12, 25 | line: 1, 26 | }, 27 | }]; 28 | return ( 29 |
    32 |

    Deno Doc Components

    33 |

    DocBlock Component Showcase

    34 | 41 | 42 | 43 | ModuleDoc - Empty 44 | 45 | {[]} 46 | 47 | 48 | ModuleDoc - No Exports 49 | 50 | {noExportsDocNodes} 51 |
    52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /_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 | -------------------------------------------------------------------------------- /_showcase/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "start": "deno run -A --watch=static/,routes/ dev.ts", 4 | "build": "deno run -A dev.ts build", 5 | "preview": "deno run -A main.ts" 6 | }, 7 | "lock": false, 8 | "importMap": "./import_map.json", 9 | "compilerOptions": { 10 | "jsx": "react-jsx", 11 | "jsxImportSource": "preact" 12 | }, 13 | "lint": { 14 | "rules": { 15 | "tags": ["fresh", "recommended"] 16 | } 17 | }, 18 | "exclude": ["**/_fresh/*"] 19 | } 20 | -------------------------------------------------------------------------------- /_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/fresh.gen.ts: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is generated by Fresh. 2 | // This file SHOULD be checked into source version control. 3 | // This file is automatically updated during development when running `dev.ts`. 4 | 5 | import * as $_app from "./routes/_app.tsx"; 6 | import * as $docblocks from "./routes/docblocks.tsx"; 7 | import * as $index from "./routes/index.tsx"; 8 | import * as $moduledoc from "./routes/moduledoc.tsx"; 9 | 10 | import { type Manifest } from "$fresh/server.ts"; 11 | 12 | const manifest = { 13 | routes: { 14 | "./routes/_app.tsx": $_app, 15 | "./routes/docblocks.tsx": $docblocks, 16 | "./routes/index.tsx": $index, 17 | "./routes/moduledoc.tsx": $moduledoc, 18 | }, 19 | islands: {}, 20 | baseUrl: import.meta.url, 21 | } satisfies Manifest; 22 | 23 | export default manifest; 24 | -------------------------------------------------------------------------------- /_showcase/import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "$fresh/": "https://deno.land/x/fresh@1.6.3/", 4 | "preact": "https://esm.sh/preact@10.19.3", 5 | "preact/": "https://esm.sh/preact@10.19.3/", 6 | "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4", 7 | "@preact/signals": "https://esm.sh/*@preact/signals@1.2.1", 8 | "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.0", 9 | "twind": "https://esm.sh/twind@0.16.19", 10 | "twind/": "https://esm.sh/twind@0.16.19/", 11 | "apiland/types": "https://deno.land/x/apiland@1.7.2/types.d.ts", 12 | "deno_doc/types": "https://deno.land/x/deno_doc@0.46.0/lib/types.d.ts", 13 | "@doc_components/": "../" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /_showcase/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import { start } from "$fresh/server.ts"; 8 | import manifest from "./fresh.gen.ts"; 9 | 10 | import twindPlugin from "$fresh/plugins/twind.ts"; 11 | import twindConfig from "./twind.config.ts"; 12 | import { setup } from "@doc_components/services.ts"; 13 | 14 | await setup({}); 15 | await start(manifest, { plugins: [twindPlugin(twindConfig)] }); 16 | -------------------------------------------------------------------------------- /_showcase/main.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | /** TODO(@kitsonk) This is TEMPORARY until we merge this branch in and then we 8 | * should move to `main.ts` */ 9 | 10 | import { start } from "$fresh/server.ts"; 11 | import manifest from "./fresh.gen.ts"; 12 | import { setup } from "@doc_components/services.ts"; 13 | 14 | import twindPlugin from "$fresh/plugins/twind.ts"; 15 | import twindConfig from "./twind.config.ts"; 16 | 17 | await setup({}); 18 | await start(manifest, { plugins: [twindPlugin(twindConfig)] }); 19 | -------------------------------------------------------------------------------- /_showcase/routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import { PageProps } from "$fresh/server.ts"; 2 | 3 | export default function App({ Component }: PageProps) { 4 | return ( 5 | 6 | 7 | 8 | 9 | _showcase 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /_showcase/routes/docblocks.tsx: -------------------------------------------------------------------------------- 1 | import { Head } from "$fresh/runtime.ts"; 2 | import { type Handlers, type PageProps } from "$fresh/server.ts"; 3 | import { type DocNode } from "@doc_components/deps.ts"; 4 | import { ShowcaseDocBlocks } from "../components/ShowcaseDocBlocks.tsx"; 5 | import { 6 | classNode, 7 | enumNode, 8 | fnNodes, 9 | interfaceNode, 10 | namespaceNode, 11 | typeAliasNode, 12 | } from "../data.ts"; 13 | 14 | type Data = DocNode[]; 15 | 16 | export default function DocBlocks({ data }: PageProps) { 17 | return ( 18 | <> 19 | 20 | doc_components Showcase | DocBlocks 21 | 22 | 23 | {data} 24 | 25 | 26 | ); 27 | } 28 | 29 | export const handler: Handlers = { 30 | GET(_req, { render }) { 31 | return render([ 32 | classNode, 33 | enumNode, 34 | interfaceNode, 35 | ...fnNodes, 36 | typeAliasNode, 37 | namespaceNode, 38 | ]); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /_showcase/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Head } from "$fresh/runtime.ts"; 2 | import { type Handlers, type PageProps } from "$fresh/server.ts"; 3 | import type { DocPageIndex, DocPageModule, DocPageSymbol } from "apiland/types"; 4 | import { Showcase } from "../components/Showcase.tsx"; 5 | 6 | interface Data { 7 | url: URL; 8 | indexPage: DocPageIndex; 9 | modulePage: DocPageModule; 10 | symbolPage: DocPageSymbol; 11 | } 12 | 13 | export default function Index({ data }: PageProps) { 14 | return ( 15 | <> 16 | 17 | doc_components Showcase 18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | export const handler: Handlers = { 25 | async GET(_req, { render }) { 26 | const base = "https://apiland.deno.dev"; 27 | const responses = await Promise.all([ 28 | fetch(`${base}/v2/pages/mod/doc/oak/v11.1.0/examples/`), 29 | fetch(`${base}/v2/pages/mod/doc/oak/v11.1.0/mod.ts`), 30 | fetch(`${base}/v2/pages/mod/doc/oak/v11.1.0/mod.ts?symbol=Application`), 31 | ]); 32 | const [indexPage, modulePage, symbolPage] = await Promise.all( 33 | responses.map((res) => res.json()), 34 | ); 35 | const url = new URL(`https://deno.land/x/oak@v11.1.0`); 36 | return render({ url, indexPage, modulePage, symbolPage }); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /_showcase/routes/moduledoc.tsx: -------------------------------------------------------------------------------- 1 | import { Head } from "$fresh/runtime.ts"; 2 | import { ShowcaseModuleDoc } from "../components/ShowcaseModuleDoc.tsx"; 3 | 4 | export default function ModuleDocs() { 5 | return ( 6 | <> 7 | 8 | doc_components Showcase | ModuleDoc 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /_showcase/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denoland/doc_components/b59ac2b100e6bde1f8f9ab75262038db27f47cd9/_showcase/static/favicon.ico -------------------------------------------------------------------------------- /_showcase/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | -------------------------------------------------------------------------------- /_showcase/util.ts: -------------------------------------------------------------------------------- 1 | export function headerify(str: string): string { 2 | return str.replaceAll(/[ -/]/g, "_").toLowerCase(); 3 | } 4 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "jsxImportSource": "preact" 5 | }, 6 | "lock": false, 7 | "importMap": "./_showcase/import_map.json", 8 | "tasks": { 9 | "check": "deno check _showcase/main.ts", 10 | "check:types": "deno check **/*.ts && deno check **/*.tsx", 11 | "ok": "deno fmt --check && deno lint && deno task check:types && deno task test", 12 | "showcase": "cd _showcase && deno task start", 13 | "test": "deno test" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | export * as comrak from "https://deno.land/x/comrak@0.1.1/mod.ts"; 4 | export type { 5 | Accessibility, 6 | ClassConstructorDef, 7 | ClassIndexSignatureDef, 8 | ClassMethodDef, 9 | ClassPropertyDef, 10 | DecoratorDef, 11 | DocNode, 12 | DocNodeClass, 13 | DocNodeEnum, 14 | DocNodeFunction, 15 | DocNodeImport, 16 | DocNodeInterface, 17 | DocNodeKind, 18 | DocNodeModuleDoc, 19 | DocNodeNamespace, 20 | DocNodeTypeAlias, 21 | DocNodeVariable, 22 | FunctionDef, 23 | InterfaceCallSignatureDef, 24 | InterfaceIndexSignatureDef, 25 | InterfaceMethodDef, 26 | InterfacePropertyDef, 27 | JsDoc, 28 | JsDocTag, 29 | JsDocTagDoc, 30 | JsDocTagKind, 31 | JsDocTagNamed, 32 | JsDocTagParam, 33 | JsDocTagReturn, 34 | JsDocTagTags, 35 | JsDocTagValued, 36 | LiteralCallSignatureDef, 37 | LiteralIndexSignatureDef, 38 | LiteralMethodDef, 39 | LiteralPropertyDef, 40 | Location, 41 | ObjectPatPropDef, 42 | ParamDef, 43 | TruePlusMinus, 44 | TsTypeDef, 45 | TsTypeIntersectionDef, 46 | TsTypeMappedDef, 47 | TsTypeParamDef, 48 | TsTypeTupleDef, 49 | TsTypeUnionDef, 50 | } from "https://deno.land/x/deno_doc@0.99.0/types.d.ts"; 51 | 52 | export { toHtml } from "https://esm.sh/hast-util-to-html@9.0.0"; 53 | export * as htmlEntities from "https://esm.sh/html-entities@2.4.0"; 54 | export { all, createLowlight } from "https://esm.sh/lowlight@3.1.0"; 55 | export { 56 | apply, 57 | type Configuration, 58 | type CSSRules, 59 | type Directive, 60 | type Plugin, 61 | setup, 62 | type ThemeConfiguration, 63 | tw, 64 | } from "twind"; 65 | export * as twColors from "twind/colors"; 66 | export { css } from "twind/css"; 67 | export { type ComponentChildren } from "preact"; 68 | -------------------------------------------------------------------------------- /deps_test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | 9 | export { assertEquals } from "https://deno.land/std@0.213.0/assert/assert_equals.ts"; 10 | -------------------------------------------------------------------------------- /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/doc.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | /** Utilities for dealing with deno_doc structures and their derivatives. 4 | * 5 | * @module 6 | */ 7 | 8 | import { type DocNode, type DocNodeKind } from "../deps.ts"; 9 | 10 | const EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]; 11 | const INDEX_MODULES = ["mod", "lib", "main", "index"].flatMap((idx) => 12 | EXT.map((ext) => `${idx}${ext}`) 13 | ); 14 | 15 | const KIND_ORDER: DocNodeKind[] = [ 16 | "namespace", 17 | "class", 18 | "interface", 19 | "typeAlias", 20 | "variable", 21 | "function", 22 | "enum", 23 | "moduleDoc", 24 | "import", 25 | ]; 26 | 27 | export function byKind(a: DocNode, b: DocNode): number { 28 | return KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind); 29 | } 30 | 31 | export function byKindValue(a: DocNodeKind, b: DocNodeKind): number { 32 | return KIND_ORDER.indexOf(a) - KIND_ORDER.indexOf(b); 33 | } 34 | 35 | /** Given a set of paths which are expected to be siblings within a folder/dir 36 | * return what appears to be the "index" module. If none can be identified, 37 | * `undefined` is returned. */ 38 | export function getIndex(paths: string[]): string | undefined { 39 | for (const index of INDEX_MODULES) { 40 | const item = paths.find((file) => file.toLowerCase().endsWith(`/${index}`)); 41 | if (item) { 42 | return item; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /doc/doc_block.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { DocBlockClass } from "./classes.tsx"; 4 | import { type DocNode, type DocNodeFunction } from "../deps.ts"; 5 | import { DocBlockEnum } from "./enums.tsx"; 6 | import { DocBlockFunction } from "./functions.tsx"; 7 | import { DocBlockInterface } from "./interfaces.tsx"; 8 | import { type Context } from "./markdown.tsx"; 9 | import { DocBlockNamespace } from "./namespaces.tsx"; 10 | import { DocBlockTypeAlias } from "./type_aliases.tsx"; 11 | import { type Child, take } from "./utils.ts"; 12 | import { DocBlockVariable } from "./variables.tsx"; 13 | 14 | export function DocBlock( 15 | { children, name, context }: { 16 | children: Child; 17 | name: string; 18 | context: Context; 19 | }, 20 | ) { 21 | const docNodes = take(children, true); 22 | const elements = []; 23 | for (const docNode of docNodes) { 24 | switch (docNode.kind) { 25 | case "class": 26 | elements.push( 27 | 28 | {docNode} 29 | , 30 | ); 31 | break; 32 | case "enum": 33 | elements.push( 34 | 35 | {docNode} 36 | , 37 | ); 38 | break; 39 | case "interface": 40 | elements.push( 41 | 42 | {docNode} 43 | , 44 | ); 45 | break; 46 | case "namespace": 47 | elements.push( 48 | 49 | {docNode} 50 | , 51 | ); 52 | break; 53 | case "typeAlias": 54 | elements.push( 55 | 56 | {docNode} 57 | , 58 | ); 59 | break; 60 | case "variable": 61 | elements.push( 62 | 63 | {docNode} 64 | , 65 | ); 66 | break; 67 | } 68 | } 69 | const fnNodes = docNodes.filter(({ kind }) => 70 | kind === "function" 71 | ) as DocNodeFunction[]; 72 | if (fnNodes.length) { 73 | elements.push( 74 | 75 | {fnNodes} 76 | , 77 | ); 78 | } 79 | return
    {elements}
    ; 80 | } 81 | -------------------------------------------------------------------------------- /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/doc_title.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { DocSubTitleClass } from "./classes.tsx"; 4 | import { type DocNode } from "../deps.ts"; 5 | import { DocSubTitleInterface } from "./interfaces.tsx"; 6 | import { type Child, decamelize, take } from "./utils.ts"; 7 | import { docNodeKindColors } from "./symbol_kind.tsx"; 8 | import { Context } from "./markdown.tsx"; 9 | 10 | export function DocTitle( 11 | { children, property, name, context }: { 12 | children: Child; 13 | property?: string; 14 | name: string; 15 | context: Context; 16 | }, 17 | ) { 18 | const docNode = take(children, true); 19 | let subTitle; 20 | switch (docNode.kind) { 21 | case "class": 22 | context.typeParams = docNode.classDef.typeParams.map(({ name }) => name); 23 | subTitle = ( 24 | 25 | {docNode} 26 | 27 | ); 28 | break; 29 | case "interface": 30 | context.typeParams = docNode.interfaceDef.typeParams.map(({ name }) => 31 | name 32 | ); 33 | subTitle = ( 34 | 35 | {docNode} 36 | 37 | ); 38 | break; 39 | } 40 | 41 | return ( 42 |
    43 |
    44 | 49 | {decamelize(property ? "method" : docNode.kind)} 50 | {" "} 51 | {property ? `${name}.${property}` : name} 52 |
    53 | {subTitle && ( 54 |
    55 | {subTitle} 56 |
    57 | )} 58 |
    59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /doc/enums.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type DocNodeEnum } from "../deps.ts"; 4 | import { DocEntry, Examples, nameToId, Section } from "./doc_common.tsx"; 5 | import { Context } from "./markdown.tsx"; 6 | import { style } from "../styles.ts"; 7 | import { TypeDef } from "./types.tsx"; 8 | import { type Child, take } from "./utils.ts"; 9 | 10 | function byName(a: { name: string }, b: { name: string }): number { 11 | return a.name.localeCompare(b.name); 12 | } 13 | 14 | export function DocBlockEnum( 15 | { children, context }: { 16 | children: Child; 17 | context: Context; 18 | }, 19 | ) { 20 | const { name: enumName, enumDef: { members }, location, jsDoc } = take( 21 | children, 22 | ); 23 | const items = [...members].sort(byName).map(({ name, init, jsDoc }) => { 24 | const id = nameToId("enum", `${enumName}_${name}`); 25 | return ( 26 | 33 | {init && ( 34 | <> 35 | {" = "} 36 | 37 | {init} 38 | 39 | 40 | )} 41 | 42 | ); 43 | }); 44 | return ( 45 |
    46 | {jsDoc} 47 |
    {items}
    48 |
    49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /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/library_doc.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { SectionTitle, tagVariants } from "./doc_common.tsx"; 4 | import * as Icons from "../icons.tsx"; 5 | import { type Context, JsDoc, Markdown } from "./markdown.tsx"; 6 | import { services } from "../services.ts"; 7 | import { style } from "../styles.ts"; 8 | import { type Child, isDeprecated, take } from "./utils.ts"; 9 | import { docNodeKindMap } from "./symbol_kind.tsx"; 10 | import { categorize, type ProcessedSymbol } from "./library_doc_panel.tsx"; 11 | import { type SymbolItem } from "./module_index_panel.tsx"; 12 | 13 | function Entry( 14 | { children, context }: { 15 | children: Child; 16 | context: Context; 17 | }, 18 | ) { 19 | const item = take(children); 20 | const href = services.resolveHref(context.url, item.name); 21 | 22 | return ( 23 | 24 | 25 |
    26 |
    27 | {item.kinds.map((kind) => docNodeKindMap[kind]())} 28 |
    29 | {item.name} 30 | 31 | {isDeprecated({ jsDoc: item.jsDoc ?? undefined }) && 32 | tagVariants.deprecated()} 33 | {item.unstable && tagVariants.unstable()} 34 | 35 |
    36 | 37 | 38 | 39 | {item.jsDoc?.doc} 40 | 41 | 42 | 43 | ); 44 | } 45 | 46 | function Section( 47 | { children, title, context }: { 48 | children: Child; 49 | title: string; 50 | context: Context; 51 | }, 52 | ) { 53 | const symbols = take(children, true); 54 | 55 | return ( 56 |
    57 | {title} 58 | 59 | {symbols.map((symbol) => ( 60 | 61 | {symbol} 62 | 63 | ))} 64 |
    65 |
    66 | ); 67 | } 68 | 69 | export function LibraryDoc( 70 | { children, sourceUrl, jsDoc, ...context }: { 71 | children: Child; 72 | jsDoc?: string; 73 | sourceUrl: string; 74 | } & Pick, 75 | ) { 76 | const items = take(children, true); 77 | 78 | const [categories, uncategorized] = categorize(items); 79 | 80 | return ( 81 |
    82 |
    83 |
    84 | 89 | 90 | 91 |
    92 |
    93 |
    94 | {jsDoc && {{ doc: jsDoc }}} 95 | 96 | {Object.entries(categories).sort(([a], [b]) => a.localeCompare(b)) 97 | .map(([category, items]) => ( 98 |
    99 | {items} 100 |
    101 | ))} 102 | {uncategorized.length !== 0 && ( 103 |
    104 | {uncategorized} 105 |
    106 | )} 107 |
    108 |
    109 |
    110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /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/markdown.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { 4 | all, 5 | comrak, 6 | createLowlight, 7 | htmlEntities, 8 | toHtml, 9 | tw, 10 | } from "../deps.ts"; 11 | import { services } from "../services.ts"; 12 | import { style, type StyleKey } from "../styles.ts"; 13 | import { assert, type Child, splitMarkdownTitle, take } from "./utils.ts"; 14 | 15 | const CODE_BLOCK_RE = 16 | /
    ([^<]+)<\/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 | -------------------------------------------------------------------------------- /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/module_index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type Context, Markdown } from "./markdown.tsx"; 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 | 9 | export interface IndexItem { 10 | kind: "dir" | "module" | "file"; 11 | path: string; 12 | size: number; 13 | ignored: boolean; 14 | doc?: string; 15 | } 16 | 17 | function Folder({ children, parent, context }: { 18 | children: Child; 19 | parent: string; 20 | context: Context; 21 | }) { 22 | const item = take(children); 23 | const url = new URL(context.url); 24 | url.pathname += item.path; 25 | const href = services.resolveHref(url); 26 | const label = item.path.slice(parent === "/" ? 1 : parent.length + 1); 27 | return ( 28 | 29 | 30 | 31 | {label} 32 | 33 | 34 | 35 | {item.doc} 36 | 37 | 38 | 39 | ); 40 | } 41 | 42 | function Module({ children, parent, context }: { 43 | children: Child; 44 | parent: string; 45 | context: Context; 46 | }) { 47 | const item = take(children); 48 | const url = new URL(context.url); 49 | url.pathname += item.path; 50 | const href = services.resolveHref(url); 51 | const label = item.path.slice(parent === "/" ? 1 : parent.length + 1); 52 | return ( 53 | 54 | 55 | 56 | {label} 57 | 58 | 59 | 60 | {item.doc} 61 | 62 | 63 | 64 | ); 65 | } 66 | 67 | const order = ["dir", "module", "file"] as const; 68 | 69 | export function ModuleIndex( 70 | { 71 | children, 72 | path = "/", 73 | skipMods = false, 74 | sourceUrl, 75 | ...context 76 | }: { 77 | children: Child; 78 | skipMods?: boolean; 79 | path?: string; 80 | sourceUrl: string; 81 | } & Pick, 82 | ) { 83 | const items = take(children, true); 84 | items.sort((a, b) => 85 | (order.indexOf(a.kind) - order.indexOf(b.kind)) || 86 | a.path.localeCompare(b.path) 87 | ); 88 | const entries = []; 89 | for (const item of items) { 90 | if (item.ignored) { 91 | continue; 92 | } 93 | if (item.kind === "dir") { 94 | entries.push( 95 | 96 | {item} 97 | , 98 | ); 99 | } else if (item.kind === "module" && !skipMods) { 100 | entries.push( 101 | 102 | {item} 103 | , 104 | ); 105 | } 106 | } 107 | 108 | if (entries.length === 0) { 109 | return null; 110 | } 111 | return ( 112 |
    113 |
    114 |
    115 | 116 | Index 117 |
    118 | 123 | 124 | 125 |
    126 | {entries}
    127 |
    128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /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/namespaces.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type DocNodeNamespace } from "../deps.ts"; 4 | import { type Context } from "./markdown.tsx"; 5 | import { DocTypeSections } from "./module_doc.tsx"; 6 | import { style } from "../styles.ts"; 7 | import { asCollection, Child, take } from "./utils.ts"; 8 | import { Examples } from "./doc_common.tsx"; 9 | 10 | export function DocBlockNamespace( 11 | { children, context }: { 12 | children: Child; 13 | context: Context; 14 | }, 15 | ) { 16 | const def = take(children); 17 | const collection = asCollection(def.namespaceDef.elements); 18 | return ( 19 |
    20 | {def.jsDoc} 21 | 22 | 23 | {collection} 24 | 25 |
    26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /doc/params.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type ComponentChildren, type ParamDef } from "../deps.ts"; 4 | import { style } from "../styles.ts"; 5 | import { TypeDef } from "./types.tsx"; 6 | import { type Child, take } from "./utils.ts"; 7 | import { Context } from "./markdown.tsx"; 8 | 9 | function Param( 10 | { children, i, context }: { 11 | children: Child; 12 | i: number; 13 | context: Context; 14 | }, 15 | ) { 16 | const param = take(children); 17 | const name = paramName(param, i); 18 | const type = param.kind === "assign" ? param.left.tsType : param.tsType; 19 | 20 | return ( 21 | 22 | {name} 23 | {((("optional" in param) && param.optional) || param.kind === "assign") && 24 | "?"} 25 | {type && ( 26 | 27 | :{" "} 28 | 29 | {type} 30 | 31 | 32 | )} 33 | 34 | ); 35 | } 36 | 37 | export function Params( 38 | { children, context }: { 39 | children: Child; 40 | context: Context; 41 | }, 42 | ) { 43 | const params = take(children, true); 44 | if (!params.length) { 45 | return null; 46 | } 47 | if (params.length < 3) { 48 | const items = []; 49 | for (let i = 0; i < params.length; i++) { 50 | items.push( 51 | 52 | {params[i]} 53 | , 54 | ); 55 | if (i < params.length - 1) { 56 | items.push({", "}); 57 | } 58 | } 59 | return {items}; 60 | } else { 61 | return ( 62 |
    63 | {params.map((param, i) => ( 64 |
    65 | 66 | {param} 67 | , 68 |
    69 | ))} 70 |
    71 | ); 72 | } 73 | } 74 | 75 | export function paramName(param: ParamDef, i: number): ComponentChildren { 76 | switch (param.kind) { 77 | case "array": 78 | case "object": 79 | return unnamed {i}; 80 | case "assign": 81 | return paramName(param.left, i); 82 | case "identifier": 83 | return param.name; 84 | case "rest": 85 | return ...{paramName(param.arg, i)}; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /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/symbol_kind.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { style } from "../styles.ts"; 4 | 5 | export const docNodeKindColors = { 6 | "namespace": ["#D25646", "#D256461A"], 7 | "class": ["#20B44B", "#2FA8501A"], 8 | "enum": ["#22ABB0", "#22ABB01A"], 9 | "variable": ["#7E57C0", "#7E57C01A"], 10 | "function": ["#056CF0", "#026BEB1A"], 11 | "interface": ["#D2A064", "#D4A0681A"], 12 | "typeAlias": ["#A4478C", "#A4478C1A"], 13 | "moduleDoc": ["", ""], 14 | "import": ["", ""], 15 | } as const; 16 | 17 | export const docNodeKindMap = { 18 | "namespace": Namespace, 19 | "class": Class, 20 | "enum": Enum, 21 | "variable": Variable, 22 | "function": Function, 23 | "interface": Interface, 24 | "typeAlias": TypeAlias, 25 | "moduleDoc": () => null, 26 | "import": () => null, 27 | } as const; 28 | 29 | export function Namespace() { 30 | const [text, bg] = docNodeKindColors["namespace"]; 31 | return ( 32 |
    33 | N 34 |
    35 | ); 36 | } 37 | 38 | export function Class() { 39 | const [text, bg] = docNodeKindColors["class"]; 40 | return ( 41 |
    45 | c 46 |
    47 | ); 48 | } 49 | 50 | export function Enum() { 51 | const [text, bg] = docNodeKindColors["enum"]; 52 | return ( 53 |
    57 | E 58 |
    59 | ); 60 | } 61 | 62 | export function Variable() { 63 | const [text, bg] = docNodeKindColors["variable"]; 64 | return ( 65 |
    69 | v 70 |
    71 | ); 72 | } 73 | 74 | export function Function() { 75 | const [text, bg] = docNodeKindColors["function"]; 76 | return ( 77 |
    81 | f 82 |
    83 | ); 84 | } 85 | 86 | export function Interface() { 87 | const [text, bg] = docNodeKindColors["interface"]; 88 | return ( 89 |
    93 | I 94 |
    95 | ); 96 | } 97 | 98 | export function TypeAlias() { 99 | const [text, bg] = docNodeKindColors["typeAlias"]; 100 | return ( 101 |
    105 | T 106 |
    107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /doc/type_aliases.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type DocNodeTypeAlias } from "../deps.ts"; 4 | import { DocEntry, Examples, nameToId, tagVariants } from "./doc_common.tsx"; 5 | import { type Context } from "./markdown.tsx"; 6 | import { style } from "../styles.ts"; 7 | import { TypeDef, TypeParamsDoc } from "./types.tsx"; 8 | import { type Child, isDeprecated, take } from "./utils.ts"; 9 | 10 | export function DocBlockTypeAlias( 11 | { children, context }: { 12 | children: Child; 13 | context: Context; 14 | }, 15 | ) { 16 | const def = take(children); 17 | const id = nameToId("typeAlias", def.name); 18 | context.typeParams = def.typeAliasDef.typeParams.map(({ name }) => name); 19 | const tags = []; 20 | if (isDeprecated(def)) { 21 | tags.push(tagVariants.deprecated()); 22 | } 23 | return ( 24 |
    25 | {def.jsDoc} 26 | 27 | 28 | {def.typeAliasDef.typeParams} 29 | 30 | 31 | 38 | :{" "} 39 | 40 | {def.typeAliasDef.tsType} 41 | 42 | 43 |
    44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /doc/usage.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { style } from "../styles.ts"; 4 | import { camelize, parseURL } from "./utils.ts"; 5 | import * as Icons from "../icons.tsx"; 6 | import { Markdown } from "./markdown.tsx"; 7 | 8 | interface ParsedUsage { 9 | /** The symbol that the item should be imported as. If `usageSymbol` and 10 | * `localVar` is defined, then the item is a named import, otherwise it is 11 | * a namespace import. */ 12 | importSymbol: string; 13 | /** The undecorated code of the generated import statement to import and use 14 | * the item. */ 15 | importStatement: string; 16 | /** The final specifier that should be used to import from. */ 17 | importSpecifier: string; 18 | /** The local variable that the `usageSymbol` should be destructured out of. 19 | */ 20 | localVar?: string; 21 | /** The symbol that should be destructured from the `localVar` which will be 22 | * bound to the item's value. */ 23 | usageSymbol?: string; 24 | } 25 | 26 | /** Given the URL and optional item and is type flag, provide back a parsed 27 | * version of the usage of an item for rendering. */ 28 | export function parseUsage( 29 | url: URL, 30 | item?: string, 31 | isType?: boolean, 32 | clearSearch = true, 33 | ): string { 34 | const parsed = parseURL(url); 35 | const target = new URL(url); 36 | if (clearSearch) { 37 | target.search = ""; 38 | } 39 | const itemParts = item?.split("."); 40 | // when the imported symbol is a namespace import, we try to guess at an 41 | // intelligent camelized name for the import based on the package name. If 42 | // it is a named import, we simply import the symbol itself. 43 | const importSymbol = itemParts 44 | ? itemParts[0] 45 | : camelize(parsed?.package ?? "mod"); 46 | // when using a symbol from an imported namespace exported from a module, we 47 | // need to create the local symbol, which we identify here. 48 | const usageSymbol = itemParts && itemParts.length > 1 49 | ? itemParts.pop() 50 | : undefined; 51 | // if it is namespaces within namespaces, we simply re-join them together 52 | // instead of trying to figure out some sort of nested restructuring 53 | const localVar = itemParts?.join("."); 54 | // we create an import statement which is used to populate the copy paste 55 | // snippet of code. 56 | let importStatement = item 57 | ? `import { ${ 58 | isType ? "type " : "" 59 | }${importSymbol} } from "${target.href}";` 60 | : `import * as ${importSymbol} from "${target.href}";`; 61 | // if we are using a symbol off a imported namespace, we need to destructure 62 | // it to a local variable. 63 | if (usageSymbol) { 64 | importStatement += `\nconst { ${usageSymbol} } = ${localVar};`; 65 | } 66 | return importStatement; 67 | } 68 | 69 | export function Usage( 70 | { url, name, isType }: { url: URL; name?: string; isType?: boolean }, 71 | ) { 72 | const importStatement = parseUsage(url, name, isType, true); 73 | const onClick = 74 | // deno-lint-ignore no-explicit-any 75 | `navigator?.clipboard?.writeText('${importStatement}');` as any; 76 | 77 | return ( 78 |
    79 | 80 | {`\`\`\`typescript\n${importStatement}\n\`\`\``} 81 | 82 | 83 |
    84 | 87 |
    88 |
    89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /doc/variables.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { type DocNodeVariable } from "../deps.ts"; 4 | import { style } from "../styles.ts"; 5 | import { TypeDef } from "./types.tsx"; 6 | import { type Child, take } from "./utils.ts"; 7 | import { Context } from "./markdown.tsx"; 8 | import { DocEntry, Examples, nameToId, Section } from "./doc_common.tsx"; 9 | 10 | export function DocBlockVariable( 11 | { children, context }: { 12 | children: Child; 13 | context: Context; 14 | }, 15 | ) { 16 | const def = take(children); 17 | const id = nameToId("variable", def.name); 18 | 19 | if (!def.variableDef.tsType) { 20 | return null; 21 | } 22 | 23 | return ( 24 |
    25 | {def.jsDoc} 26 | 27 |
    28 | {[ 29 | 30 | 31 | {def.variableDef.tsType} 32 | 33 | , 34 | ]} 35 |
    36 |
    37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /doc_test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { assertEquals } from "./deps_test.ts"; 4 | import { type DocNode } from "./deps.ts"; 5 | import { byKind } from "./doc/doc.ts"; 6 | import { processProperty, splitMarkdownTitle } from "./doc/utils.ts"; 7 | 8 | Deno.test({ 9 | name: "doc - sort by kind", 10 | fn() { 11 | const fixtures: DocNode[] = [ 12 | { 13 | name: "namespace", 14 | kind: "namespace", 15 | namespaceDef: { elements: [] }, 16 | location: { filename: "", line: 0, col: 0 }, 17 | declarationKind: "export", 18 | }, 19 | { 20 | name: "fn", 21 | kind: "function", 22 | functionDef: { 23 | params: [], 24 | isAsync: false, 25 | isGenerator: false, 26 | typeParams: [], 27 | }, 28 | location: { filename: "", line: 0, col: 0 }, 29 | declarationKind: "export", 30 | }, 31 | { 32 | name: "A", 33 | kind: "interface", 34 | interfaceDef: { 35 | extends: [], 36 | methods: [], 37 | properties: [], 38 | callSignatures: [], 39 | indexSignatures: [], 40 | typeParams: [], 41 | }, 42 | location: { filename: "", line: 0, col: 0 }, 43 | declarationKind: "export", 44 | }, 45 | ]; 46 | fixtures.sort(byKind); 47 | assertEquals(fixtures.map(({ kind }) => kind), [ 48 | "namespace", 49 | "interface", 50 | "function", 51 | ]); 52 | }, 53 | }); 54 | 55 | Deno.test("splitMarkdownTitle - simple", () => { 56 | const markdown = `some text 57 | 58 | \`\`\` 59 | // comment 60 | \`\`\` 61 | `; 62 | const [summary, body] = splitMarkdownTitle(markdown); 63 | assertEquals(summary, "some text"); 64 | assertEquals( 65 | body, 66 | `\`\`\` 67 | // comment 68 | \`\`\``, 69 | ); 70 | }); 71 | 72 | Deno.test("splitMarkdownTitle - markdown only", () => { 73 | const markdown = `\`\`\` 74 | // comment 75 | \`\`\` 76 | `; 77 | const [summary, body] = splitMarkdownTitle(markdown); 78 | assertEquals(summary, ""); 79 | assertEquals( 80 | body, 81 | `\`\`\` 82 | // comment 83 | \`\`\``, 84 | ); 85 | }); 86 | 87 | Deno.test("splitMarkdownTitle - summary only", () => { 88 | const markdown = "some text"; 89 | const [summary, body] = splitMarkdownTitle(markdown); 90 | assertEquals(summary, "some text"); 91 | assertEquals(body, ""); 92 | }); 93 | 94 | Deno.test("splitMarkdownTitle - paragraphs only", () => { 95 | const markdown = `some text 96 | 97 | hello world`; 98 | const [summary, body] = splitMarkdownTitle(markdown); 99 | assertEquals(summary, "some text"); 100 | assertEquals(body, "hello world"); 101 | }); 102 | 103 | Deno.test("splitMarkdownTitle - tight", () => { 104 | const markdown = `some text 105 | \`\`\` 106 | // comment 107 | \`\`\` 108 | `; 109 | const [summary, body] = splitMarkdownTitle(markdown); 110 | assertEquals(summary, "some text"); 111 | assertEquals( 112 | body, 113 | `\`\`\` 114 | // comment 115 | \`\`\``, 116 | ); 117 | }); 118 | 119 | Deno.test("processProperty - prototype", () => { 120 | const [property, isPrototype] = processProperty("prototype.foo"); 121 | assertEquals(property, "foo"); 122 | assertEquals(isPrototype, true); 123 | }); 124 | 125 | Deno.test("processProperty - static", () => { 126 | const [property, isPrototype] = processProperty("foo"); 127 | assertEquals(property, "foo"); 128 | assertEquals(isPrototype, false); 129 | }); 130 | 131 | Deno.test("processProperty - prototype compute", () => { 132 | const [property, isPrototype] = processProperty( 133 | "prototype.[Symbol.iterator]", 134 | ); 135 | assertEquals(property, "[Symbol.iterator]"); 136 | assertEquals(isPrototype, true); 137 | }); 138 | 139 | Deno.test("processProperty - static compute", () => { 140 | const [property, isPrototype] = processProperty("[Symbol.iterator]"); 141 | assertEquals(property, "[Symbol.iterator]"); 142 | assertEquals(isPrototype, false); 143 | }); 144 | -------------------------------------------------------------------------------- /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 | 13 | 17 | 18 | ); 19 | } 20 | 21 | export function Source(props: { class?: string }) { 22 | return ( 23 | 31 | 38 | 45 | 46 | ); 47 | } 48 | 49 | export function Index() { 50 | return ( 51 | 58 | 64 | 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 | 86 | 87 | 88 | ); 89 | } 90 | 91 | export function Copy() { 92 | return ( 93 | 100 | 104 | 105 | ); 106 | } 107 | 108 | export function GitHub(props: { class?: string }) { 109 | return ( 110 | 115 | 120 | 121 | ); 122 | } 123 | 124 | export function Discord(props: { class?: string }) { 125 | return ( 126 | 131 | 132 | 133 | ); 134 | } 135 | 136 | export function Twitter(props: { class?: string }) { 137 | return ( 138 | 143 | 144 | 145 | ); 146 | } 147 | 148 | export function Plus() { 149 | return ( 150 | 157 | 166 | 175 | 176 | ); 177 | } 178 | 179 | export function Minus(props: { class?: string }) { 180 | return ( 181 | 189 | 198 | 199 | ); 200 | } 201 | 202 | export function TrashCan(props: { class?: string }) { 203 | return ( 204 | 212 | 218 | 219 | ); 220 | } 221 | 222 | export function ExclamationMark(props: { class?: string }) { 223 | return ( 224 | 232 | 240 | 249 | 250 | ); 251 | } 252 | 253 | export function Logo(props: { class?: string }) { // Size not normalized 254 | return ( 255 | 262 | 263 | 267 | 271 | 275 | 279 | 283 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | ); 295 | } 296 | 297 | export function Deno(props: { class?: string }) { // Size not normalized 298 | return ( 299 | 306 | 307 | 311 | 315 | 319 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | ); 331 | } 332 | 333 | export function Link(props: { class?: string }) { 334 | return ( 335 | 343 | 344 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | ); 358 | } 359 | 360 | export function LinkLine(props: { class?: string }) { 361 | return ( 362 | 370 | 377 | 384 | 385 | ); 386 | } 387 | 388 | export function YouTube(props: { class?: string }) { 389 | return ( 390 | 399 | 403 | 404 | ); 405 | } 406 | 407 | export function Mastodon(props: { class?: string }) { 408 | return ( 409 | 417 | 423 | 424 | ); 425 | } 426 | 427 | export function Menu(props: { class?: string }) { // Size not normalized 428 | return ( 429 | 437 | 445 | 453 | 461 | 462 | ); 463 | } 464 | 465 | export function Cross(props: { class?: string }) { // Size not normalized 466 | return ( 467 | 473 | 479 | 480 | ); 481 | } 482 | -------------------------------------------------------------------------------- /services.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { 4 | comrak, 5 | type Configuration as TwConfiguration, 6 | type CSSRules, 7 | type Directive, 8 | setup as twSetup, 9 | } from "./deps.ts"; 10 | import { mdToHtml } from "./doc/markdown.tsx"; 11 | import { comrakStyles } from "./styles.ts"; 12 | 13 | export interface Configuration { 14 | /** Called when the doc components are trying to resolve a symbol. The 15 | * current url is provided as a string, an optional namespace and the symbol 16 | * name attempting to be resolved. 17 | * 18 | * If provided the namespace, any nested namespaces will be separated by a 19 | * `.`. 20 | * 21 | * Implementors should search the scope of the current module and namespace 22 | * ascending to global scopes to resolve the href. If the symbol cannot be 23 | * found, the function should return `undefined`. */ 24 | lookupHref?: ( 25 | url: URL, 26 | namespace: string | undefined, 27 | symbol: string, 28 | ) => string | undefined; 29 | /** Called when the doc components are trying to generate a link to a path, 30 | * module or symbol within a module. The URL to the path or module will be 31 | * provided, and the symbol will be provided. If the symbol contains `.`, 32 | * the symbol is located within a namespace in the file. 33 | * 34 | * Implementors should return a string which will be used as the `href` value 35 | * for a link. */ 36 | resolveHref?: ( 37 | url: URL, 38 | symbol?: string, 39 | namespace?: string, 40 | property?: string, 41 | ) => string; 42 | /** Called when doc components are trying to generate a link to a source file. 43 | * 44 | * Implementors should return a string which which will be used as the `href` 45 | * value for a link to the source code view of a file. If no source file can 46 | * be resolved, `undefined` should be returned. */ 47 | resolveSourceHref?: (url: string, line?: number) => string; 48 | /** Called when markdown needs to be rendered. */ 49 | markdownToHTML?: (markdown: string) => string; 50 | /** If provided, the twind {@linkcode twSetup setup} will be performed. */ 51 | tw?: TwConfiguration; 52 | /** Class to give to markdown blocks */ 53 | markdownStyle?: string | Directive; 54 | /** Class to give to markdown summary blocks */ 55 | markdownSummaryStyle?: string | Directive; 56 | } 57 | 58 | const runtimeConfig: Required< 59 | Pick< 60 | Configuration, 61 | | "resolveHref" 62 | | "lookupHref" 63 | | "resolveSourceHref" 64 | | "markdownToHTML" 65 | | "markdownStyle" 66 | | "markdownSummaryStyle" 67 | > 68 | > = { 69 | resolveHref(current, symbol, _namespace, property) { 70 | return symbol 71 | ? (property 72 | ? `/${current}/~/${symbol}/~/${property}` 73 | : `/${current}/~/${symbol}`) 74 | : `/${current}`; 75 | }, 76 | lookupHref(current, namespace, symbol) { 77 | return namespace 78 | ? `/${current}/~/${namespace}.${symbol}` 79 | : `/${current}/~/${symbol}`; 80 | }, 81 | resolveSourceHref(url, line) { 82 | return line ? `${url}#L${line}` : url; 83 | }, 84 | markdownToHTML: mdToHtml, 85 | markdownStyle: comrakStyles, 86 | markdownSummaryStyle: "", 87 | }; 88 | 89 | /** Setup the services used by the doc components. */ 90 | export async function setup(config: Configuration) { 91 | const { tw, ...other } = config; 92 | Object.assign(runtimeConfig, other); 93 | if (tw) { 94 | twSetup(tw); 95 | } 96 | if (!other.markdownToHTML) { 97 | await comrak.init(); 98 | } 99 | } 100 | 101 | export const services = { 102 | /** Return a link to the provided URL and optional symbol. */ 103 | get resolveHref(): ( 104 | url: URL, 105 | symbol?: string, 106 | namespace?: string, 107 | property?: string, 108 | ) => string { 109 | return runtimeConfig.resolveHref; 110 | }, 111 | 112 | /** Attempt to find a link to a specific symbol from the current URL and 113 | * optionally namespace. */ 114 | get lookupHref(): ( 115 | url: URL, 116 | namespace: string | undefined, 117 | symbol: string, 118 | ) => string | undefined { 119 | return runtimeConfig.lookupHref; 120 | }, 121 | 122 | get resolveSourceHref(): (url: string, line?: number) => string | undefined { 123 | return runtimeConfig.resolveSourceHref; 124 | }, 125 | 126 | /** Render Markdown to HTML */ 127 | get markdownToHTML(): (markdown: string) => string { 128 | return runtimeConfig.markdownToHTML; 129 | }, 130 | 131 | /** Class to give to markdown blocks */ 132 | get markdownStyle(): string | Directive { 133 | return runtimeConfig.markdownStyle; 134 | }, 135 | /** Class to give to markdown summary blocks */ 136 | get markdownSummaryStyle(): string | Directive { 137 | return runtimeConfig.markdownSummaryStyle; 138 | }, 139 | }; 140 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /twind.config.ts: -------------------------------------------------------------------------------- 1 | import { apply, type Plugin, type ThemeConfiguration } from "twind"; 2 | import * as twColors from "twind/colors"; 3 | import { css } from "twind/css"; 4 | 5 | export const theme: ThemeConfiguration = { 6 | colors: { 7 | transparent: "transparent", 8 | current: "currentColor", 9 | ...twColors, 10 | }, 11 | fontFamily: { 12 | mono: [ 13 | "Menlo", 14 | "Monaco", 15 | '"Lucida Console"', 16 | "Consolas", 17 | '"Liberation Mono"', 18 | '"Courier New"', 19 | "monospace", 20 | ], 21 | }, 22 | extend: { 23 | spacing: { 24 | 4.5: "1.125rem", 25 | 18: "4.5rem", 26 | 72: "18rem", 27 | }, 28 | backgroundSize: { 29 | "4": "1rem", 30 | }, 31 | }, 32 | }; 33 | 34 | export const plugins: Record = { 35 | link: 36 | apply`text-blue-600 transition duration-75 ease-in-out hover:text-blue-400`, 37 | "section-x-inset": (parts) => 38 | parts[0] === "none" 39 | ? apply`max-w-none mx-0 px-0` 40 | : apply`max-w-screen-${parts[0]} mx-auto px-6 md:px-8 lg:px-10 xl:px-14`, 41 | "divide-incl-y": (parts) => 42 | css({ 43 | "& > *": { 44 | "&:first-child": { 45 | "border-top-width": (parts[0] ?? 1) + "px", 46 | }, 47 | "border-top-width": "0px", 48 | "border-bottom-width": (parts[0] ?? 1) + "px", 49 | }, 50 | }), 51 | "icon-button": apply`border border-gray-300 rounded p-2 hover:bg-gray-100`, 52 | }; 53 | --------------------------------------------------------------------------------