@ng-doc/utils 22.0.0-beta.2 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,37 +1,28 @@
1
- <!-- PROJECT LOGO -->
2
- <br />
3
- <div align="center">
4
- <a href="https://github.com/ng-doc/ng-doc">
5
- <img src="https://ng-doc.com/assets/images/ng-doc.svg?raw=true" alt="Logo" height="150px">
6
- </a>
7
-
8
- <h1 align="center" style="margin-bottom: 0; border-bottom: 0">NgDoc</h1>
9
- <p align="center">
10
- Create user-friendly documentation for your projects with ease!
11
- <br />
12
- <a href="https://ng-doc.com/">Documentation</a>
13
- ·
14
- <a href="https://github.com/ng-doc/ng-doc/issues/new/choose">Report Bug</a>
15
- ·
16
- <a href="https://github.com/ng-doc/ng-doc/issues/new/choose">Request Feature</a>
17
-
18
- [![GitHub Workflow Status][build-shield]][build-url]
1
+ # 🧰 @ng-doc/utils
2
+
3
+ Small utilities shared by the other NgDoc packages.
4
+
5
+ It's part of [NgDoc](https://ng-doc.com/) 📚, the documentation engine for Angular projects.
6
+
19
7
  [![NPM][npm-shield]][npm-url]
20
8
  [![MIT License][license-shield]][license-url]
21
9
 
22
- </p>
23
- </div>
10
+ ## 🚀 Getting started
11
+
12
+ You don't need to install this package by hand. Add NgDoc to your project and it sets up
13
+ everything it needs:
24
14
 
25
- <!-- ABOUT THE PROJECT -->
15
+ ```bash
16
+ ng add @ng-doc/add
17
+ ```
26
18
 
27
- ## About The Project
19
+ ## 📖 Learn more
28
20
 
29
- This library is a part of NgDoc project, that uses to create documentation
30
- for Angular projects, please check our [repo](https://github.com/ng-doc/ng-doc) for more details.
21
+ - [Documentation](https://ng-doc.com/)
22
+ - [GitHub repository](https://github.com/ng-doc/ng-doc)
23
+ - [Report a bug or request a feature](https://github.com/ng-doc/ng-doc/issues/new/choose)
31
24
 
32
- [npm-shield]: https://img.shields.io/npm/v/@ng-doc/builder.svg?style=for-the-badge
33
- [npm-url]: https://www.npmjs.com/package/@ng-doc/builder
25
+ [npm-shield]: https://img.shields.io/npm/v/%40ng-doc%2Futils.svg?style=for-the-badge
26
+ [npm-url]: https://www.npmjs.com/package/@ng-doc/utils
34
27
  [license-shield]: https://img.shields.io/github/license/ng-doc/ng-doc.svg?style=for-the-badge
35
- [license-url]: https://github.com/othneildrew/Best-README-Template/blob/master/LICENSE.txt
36
- [build-shield]: https://img.shields.io/github/actions/workflow/status/ng-doc/ng-doc/release.yml?style=for-the-badge&branch=release
37
- [build-url]: https://github.com/ng-doc/ng-doc/actions
28
+ [license-url]: https://github.com/ng-doc/ng-doc/blob/main/LICENSE
@@ -0,0 +1,83 @@
1
+ import type { Root } from 'hast';
2
+ /**
3
+ * Everything a highlighted code block depends on, apart from the installed Shiki release. A cache
4
+ * keys a block by it (plus that release) with {@link NgDocHighlightCache.key}.
5
+ */
6
+ export interface NgDocHighlightBlock {
7
+ /** The plugin's format and the fixed Shiki options it highlights with. */
8
+ readonly options: Readonly<Record<string, string | number | boolean>>;
9
+ /** The theme names. NgDoc's own theme (`css-variables`) is not a Shiki theme. */
10
+ readonly themes: {
11
+ readonly light: string;
12
+ readonly dark: string;
13
+ };
14
+ /** The identity of the extra languages (`processHtml`'s `langs`); absent without any. */
15
+ readonly languages?: string;
16
+ /** The language of the block's class, or the default language: before the fallback. */
17
+ readonly lang: string;
18
+ /** The raw meta string, before it is parsed. */
19
+ readonly meta: string;
20
+ /** The block's text. */
21
+ readonly code: string;
22
+ }
23
+ /**
24
+ * A cache of highlighted code blocks for `processHtml`. Values are the JSON of the HAST nodes that
25
+ * replace a block's `pre` element: `@shikijs/rehype` replaces it with the root of the highlighted
26
+ * fragment, which holds the new `pre` element. A value that is not a JSON array of HAST elements or
27
+ * such roots is a miss. The cache only ever receives values of blocks that were highlighted without
28
+ * an error.
29
+ */
30
+ export interface NgDocHighlightCache {
31
+ /** The key of a block. It must change whenever anything in the block or the Shiki release does. */
32
+ key(block: NgDocHighlightBlock): string;
33
+ get(key: string): string | undefined;
34
+ set(key: string, value: string): void;
35
+ /**
36
+ * Whether these themes are known to load, because the cache holds a block highlighted with them.
37
+ * Without that, a document is processed only after Shiki has loaded them, as without a cache, so
38
+ * that a theme that cannot load fails every document, even one without code.
39
+ */
40
+ loads?(themes: NgDocHighlightBlock['themes']): boolean;
41
+ /** Highlights every hit again, uses the fresh value and reports each difference. */
42
+ readonly verify?: boolean;
43
+ /** A hit whose fresh value differs (with `verify`). */
44
+ mismatch?(key: string): void;
45
+ }
46
+ /** The options of {@link cachedShikiPlugin}. */
47
+ export interface CachedShikiOptions {
48
+ cache: NgDocHighlightCache;
49
+ themes: NgDocHighlightBlock['themes'];
50
+ /** The identity of the extra languages the transformer highlights with; absent without any. */
51
+ languages?: string;
52
+ /**
53
+ * `used` when the transformer loads only the grammars blocks use. It gives the same result, so
54
+ * it is not part of a key; it only selects the transformer.
55
+ */
56
+ grammars?: 'used';
57
+ /** What the options stand for in a key: change it whenever the options or this plugin change. */
58
+ identity: NgDocHighlightBlock['options'];
59
+ /** The language of a block without a `language-*` class (the options' `defaultLanguage`). */
60
+ defaultLanguage: string;
61
+ /**
62
+ * Creates the `@shikijs/rehype` transformer that highlights a miss, with the plugin's options;
63
+ * called once per thread, theme pair and languages.
64
+ */
65
+ transformer: () => Highlighter;
66
+ }
67
+ type Highlighter = (tree: Root) => Promise<void> | void;
68
+ /**
69
+ * The plain `@shikijs/rehype` plugin, with a cache of its results per block.
70
+ *
71
+ * It selects the blocks exactly as the plugin does (`pre` elements whose first child is a `code`
72
+ * element with properties, with the language of its `language-*` class or the default language)
73
+ * and leaves the tree unchanged while it keys them. A miss is highlighted by the plugin's own
74
+ * transformer, on a tree that holds only that block, so it gets exactly the plugin's options,
75
+ * language fallback, meta parsing and transformers. A failing block throws the plugin's error at
76
+ * the same block, since a hit cannot fail (the cache holds only blocks that were highlighted), and
77
+ * nothing is stored for it. Every value is serialized before the tree is changed, and every block
78
+ * is replaced with nodes parsed from it, so later plugins, which change the tree, never change a
79
+ * value or a node another block shares. A document whose blocks all hit (or that has none) never
80
+ * loads Shiki once the cache shows that its themes load.
81
+ */
82
+ export default function cachedShikiPlugin(settings: CachedShikiOptions): (tree: Root) => Promise<void>;
83
+ export {};
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Where a keyword may link inside code.
3
+ *
4
+ * The keywords plugin splits code into words and looks each one up. A word that happens to be a
5
+ * keyword is not always a reference to it: an object key (`group: 'Colors'`), a property after a
6
+ * dot, a string, an HTML attribute value or a file name in inline code (`vite.config.mjs`) only
7
+ * share its name. Highlighted code carries no token scopes (Shiki emits colours only, and adjacent
8
+ * tokens of one colour are merged), so this module reads the position of a word from the code text
9
+ * itself with a small lexer: strings and comments, the characters around the word, and the names
10
+ * the code declares. The result only depends on the code, never on the keyword set, so the words a
11
+ * page records as used keywords are exactly the ones that may link.
12
+ */
13
+ /** How the code around a word is read. */
14
+ export type CodeMode = 'typescript' | 'html';
15
+ /** What the plugin does with one word. */
16
+ export type WordPosition =
17
+ /** Not a reference: the word stays plain text and is not recorded as used. */
18
+ {
19
+ readonly link: false;
20
+ }
21
+ /**
22
+ * A reference. `keyword`, when present, is the key to look up instead of the word's own key: a
23
+ * member after a dot is looked up as `Owner.member`.
24
+ */
25
+ | {
26
+ readonly link: true;
27
+ readonly keyword?: string;
28
+ };
29
+ /** The positions of the words of one code element. */
30
+ export interface CodeScan {
31
+ /** Whether the whole element is a single keyword reference (inline code only). */
32
+ readonly whole: boolean;
33
+ /**
34
+ * The position of the word at `[start, end)` of the element's text.
35
+ * @param start - The offset of the word's first character.
36
+ * @param end - The offset after the word's last character.
37
+ * @param key - The word's key: the word without its `.member`/`#anchor` and query.
38
+ */
39
+ position(start: number, end: number, key: string): WordPosition;
40
+ }
41
+ /**
42
+ * The positions of the words of inline code, or `undefined` when no word of it may link.
43
+ *
44
+ * Inline code links when it is one keyword reference as a whole (`NgDocPage`, `*GuidePage#anchor`,
45
+ * `NgDocPage.title`, with an optional `?query`), or when it reads as TypeScript: it contains code
46
+ * punctuation (a call, generics, a union, an array type, a decorator, a type annotation) or starts
47
+ * with a declaration. Then its words link where they would in a TypeScript code block. Anything
48
+ * else (a file name like `vite.ng-doc.config.mjs`, a path, a command, prose, an HTML tag) stays
49
+ * plain text.
50
+ * @param text - The text of the inline code element.
51
+ */
52
+ export declare function scanInlineCode(text: string): CodeScan | undefined;
53
+ /**
54
+ * The positions of the words of a code block.
55
+ * @param text - The text of the block's `code` element.
56
+ * @param mode - How the block's language is read.
57
+ */
58
+ export declare function scanCodeBlock(text: string, mode: CodeMode): CodeScan;
@@ -1,9 +1,36 @@
1
1
  import { NgDocHeading, NgDocPageAnchor } from '@ng-doc/core';
2
+ import { NgDocHighlightCache } from './plugins/cached-shiki.plugin';
3
+ export type { NgDocHighlightBlock, NgDocHighlightCache } from './plugins/cached-shiki.plugin';
4
+ /** A Shiki language registration: a TextMate grammar as plain JSON, with its name. */
5
+ export interface NgDocHighlightLanguage {
6
+ readonly name: string;
7
+ readonly scopeName: string;
8
+ }
2
9
  export interface NgDocHtmlProcessorConfig {
3
10
  lightTheme?: string;
4
11
  darkTheme?: string;
12
+ /**
13
+ * Languages that code blocks can use besides those bundled with Shiki. A registration named like
14
+ * a bundled language replaces it. With languages, code is highlighted by a highlighter of its
15
+ * own, created once per thread for these languages and themes.
16
+ */
17
+ langs?: readonly NgDocHighlightLanguage[];
18
+ /**
19
+ * Which bundled Shiki grammars the highlighter loads: `all` (the default) sets it up with every
20
+ * bundled grammar, as `@shikijs/rehype` does; `used` loads only the grammars the document's code
21
+ * blocks can reach, into a highlighter kept per thread and theme pair, which takes a fraction of
22
+ * the time and gives the same HTML. With `langs`, every grammar is loaded.
23
+ */
24
+ grammars?: 'all' | 'used';
5
25
  headings?: NgDocHeading[];
6
26
  route?: string;
27
+ /**
28
+ * A cache of highlighted code blocks. With it, code is highlighted by a Shiki highlighter that is
29
+ * set up once per thread and theme pair, and a block is highlighted only when the cache misses;
30
+ * the result is the same as without it. Without it, every call sets the highlighter up again
31
+ * and highlights every block.
32
+ */
33
+ highlight?: NgDocHighlightCache;
7
34
  }
8
35
  export interface NgDocHtmlProcessorOutput {
9
36
  content: string;
@@ -0,0 +1,58 @@
1
+ import type { Root } from 'hast';
2
+ import { type HighlighterGeneric, type LanguageRegistration, createHighlighter } from 'shiki';
3
+ /**
4
+ * Loading only the Shiki grammars that code blocks use, with the same result as loading every
5
+ * bundled grammar.
6
+ *
7
+ * `@shikijs/rehype` sets its highlighter up with every bundled grammar, which takes seconds. A
8
+ * grammar's highlighting depends only on the grammars it can reach: the languages it embeds
9
+ * (`embeddedLangs`, and `embeddedLangsLazy`, which it uses once they are loaded), the scopes its
10
+ * rules include, and the grammars that inject into its scopes (`injectTo`, matched by scope
11
+ * prefix, in registration order). {@link ShikiGrammars.closure} is that set, closed under the
12
+ * same rules, plus every grammar that shares an injection target with an injector in it, so the
13
+ * injections of a scope are always registered together. Loaded in the order in which loading every
14
+ * bundled grammar registers them (the first mention of each name), a closure gives the
15
+ * highlighter the same grammars, injections and injection order for its languages as loading
16
+ * everything, whatever else was loaded before: a highlighter only grows by whole closures, and
17
+ * each scope's grammars are complete when the scope is first loaded.
18
+ */
19
+ export declare class ShikiGrammars {
20
+ private readonly byName;
21
+ private readonly byScope;
22
+ private readonly injectors;
23
+ private readonly order;
24
+ private readonly closures;
25
+ private readonly loaded;
26
+ private constructor();
27
+ /**
28
+ * The bundled grammars, in the order `@shikijs/rehype` registers them: every bundled language
29
+ * name, each with the registrations of its module.
30
+ */
31
+ static create(): Promise<ShikiGrammars>;
32
+ /**
33
+ * The grammars that a block of `language` can reach, in registration order; empty for a
34
+ * language that no bundled grammar names (it falls back to plain text, as before).
35
+ * @param language
36
+ */
37
+ closure(language: string): readonly LanguageRegistration[];
38
+ /**
39
+ * Loads the closures of `languages` that `highlighter` does not hold yet.
40
+ * @param highlighter
41
+ * @param languages
42
+ */
43
+ load(highlighter: HighlighterGeneric<string, string>, languages: Iterable<string>): void;
44
+ }
45
+ /** The bundled grammars of this thread, read once. */
46
+ export declare function shikiGrammars(): Promise<ShikiGrammars>;
47
+ /**
48
+ * A highlighter with the given themes and no language: {@link ShikiGrammars.load} adds them.
49
+ * @param themes
50
+ */
51
+ export declare function createGrammarlessHighlighter(themes: Parameters<typeof createHighlighter>[0]['themes']): Promise<HighlighterGeneric<string, string>>;
52
+ /**
53
+ * The language of every block `@shikijs/rehype` highlights in `tree` (a `pre` whose first child is
54
+ * a `code` element with properties): its `language-*` class, or `defaultLanguage`.
55
+ * @param tree
56
+ * @param defaultLanguage
57
+ */
58
+ export declare function blockLanguages(tree: Root, defaultLanguage: string): Set<string>;