From a981e397a1bcce46428589c9acec3150090ada69 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Fri, 9 Jul 2021 22:00:03 -0700 Subject: [PATCH 01/14] docs: add guide for working with syntax trees in TypeScript --- doc/learn/syntax-trees-with-typescript.md | 523 ++++++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 doc/learn/syntax-trees-with-typescript.md diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md new file mode 100644 index 00000000000..490cc3960e0 --- /dev/null +++ b/doc/learn/syntax-trees-with-typescript.md @@ -0,0 +1,523 @@ +--- +group: guide +title: Working with syntax trees in TypeScript +description: Guide that shows how to traverse, update, and create syntax trees in TypeScript +author: Christian Murphy +authorGithub: ChristianMurphy +tags: + - typescript +published: 2020-06-09 +modified: 2020-06-09 +--- + +## Working with syntax trees in TypeScript + +This guide will introduce you to: + +* [The basic syntax tree types](#the-basics) + * [`Node`](#node) + * [`Literal`](#literal) + * [`Parent`](#parent) +* [How to include syntax tree typings for a specific language](#the-languages) + * [MDAST (Markdown)](#mdast-markdown) + * [HAST (HTML)](#hast-html) + * [XAST (XML)](#xast-xml) +* [How to traverse a syntax tree](#how-to-traverse-a-syntax-tree) + * [`unist-util-visit`](#unist-util-visit) + * [`unist-util-visit-parents`](#unist-util-visit-parents) + * [`unist-util-select`](#unist-util-select) +* [How to narrow generic `Node`s to specific syntax types](#how-to-narrow-generic-node-to-specific-syntax-types) +* [How to build a syntax tree](#how-to-build-syntax-tree) + * [JSON](#json) + * [`unist-util-builder`](#unist-util-builder) + * [`hastscript`](#hastscript) + * [`xastscript`](#xastscript) + +### The Basics + +All unified syntax trees are based off [the **Uni**versal **S**yntax **T**ree (`unist`)](https://github.com/syntax-tree/unist). +Unist is a types only package available on npm at [`@types/unist`](https://www.npmjs.com/package/@types/unist), and provides three interfaces which the rest of unified's syntax trees build on: `Node`, `Literal`, and `Parent`. + +#### Node + +Every unified extends `Node`, it is the syntactic unit of syntax trees. +Every `Node` must have a `type`, the `type` tells us what kind of syntax the `Node` is. +For example in Markdown (mdast) `Node` will be extended to make different kinds of content such a `heading` or a `link`, a `paragraph` or a `blockquote` (among others). +Each of these different types extend `Node` (sometimes indirectly through `Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). + +A `Node` also can optionally include `Data`. +`Data` is a dictionary/object which can store extra information and metadata which is not standard to a given node `type`. + +When a syntax tree is parsed from a file, it may include `Position`, which is information about where this `Node` is found in the source file. + +```ts +/** + * Syntactic units in unist syntax trees are called nodes. + */ +interface Node { + /** + * The variant of a node. + */ + type: string; + + /** + * Information from the ecosystem. + */ + data?: Data | undefined; + + /** + * Location of a node in a source document. + * Must not be present if a node is generated. + */ + position?: Position | undefined; +} + +/** + * Information associated by the ecosystem with the node. + * Space is guaranteed to never be specified by unist or specifications + * implementing unist. + */ +export interface Data { + [key: string]: unknown; +} + +/** + * Location of a node in a source file. + */ +export interface Position { + /** + * Place of the first character of the parsed source region. + */ + start: Point; + + /** + * Place of the first character after the parsed source region. + */ + end: Point; + + /** + * Start column at each index (plus start line) in the source region, + * for elements that span multiple lines. + */ + indent?: number[] | undefined; +} +``` + +#### Literal + +`Literal` extends `Node` and adds a `value` property. +For example a Markdown `text` `Node` extends `Literal` and sets `value` to be a `string`. + +```ts +/** + * Nodes containing a value. + */ +export interface Literal extends Node { + value: unknown; +} +``` + +#### Parent + + +```ts +/** + * Nodes containing other nodes. + */ +export interface Parent extends Node { + /** + * List representing the children of a node. + */ + children: Node[]; +} +``` + +#### Pulling unist into a project + +```bash +npm install --save-dev @types/unist +``` + +then import the types into a TypeScript file with: + +```ts +import type {Node, Literal, Parent} from 'unist' +``` + +or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: + +```js +/** + * @typedef {import("unist").Node} Node + * @typedef {import("unist").Literal} Literal + * @typedef {import("unist").Parent} Parent + */ +``` + +### The Languages + +`unist` provides the building blocks for having consistent structure, but is often more generic than what we as developers and content authors want to work with. +We want to work with a specific language like Markdown, HTML, XML, and others. +Each of these languages has it's own syntax tree standard which extends `unist` with more specific content types. +Let's take a look at a few of Unified's compatible languages. + +#### MDAST (Markdown) + +The [**M**arkdown **A**bstract **S**yntax **T**ree (MDAST)](https://github.com/syntax-tree/mdast#readme) extends `unist` with types specific for Markdown such as [`Heading`](https://github.com/syntax-tree/mdast#heading), [`Code`](https://github.com/syntax-tree/mdast#code), [`Link`](https://github.com/syntax-tree/mdast#link), and many more. +A full list of node types can be found in the [MDAST documentation](https://github.com/syntax-tree/mdast#readme). +[Typings for MDAST are available on npm](https://www.npmjs.com/package/@types/mdast) can be installed with a package manager. + +```bash +npm install --save-dev @types/mdast +``` + +then import the types into a TypeScript file with: + +```ts +import type {Heading, Code, Link} from 'mdast' +``` + +or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: + +```js +/** + * @typedef {import("mdast").Heading} Heading + * @typedef {import("mdast").Code} Code + * @typedef {import("mdast").Link} Link + */ +``` + +#### HAST (HTML) + +The [**H**ypertext **A**bstract **S**yntax **T**ree (HAST)](https://github.com/syntax-tree/hast#readme) extends `unist` with types specific for HTML such as [`Element`](https://github.com/syntax-tree/hast#element), [`Comment`](https://github.com/syntax-tree/hast#comment), [`DocType`](https://github.com/syntax-tree/hast#doctype), and many more. +A full list of node types can be found in the [HAST documentation](https://github.com/syntax-tree/hast#readme). +[Typings for HAST are available on npm](https://www.npmjs.com/package/@types/hast) can be installed with a package manager. + +```bash +npm install --save-dev @types/hast +``` + +then import the types into a TypeScript file with: + +```ts +import type {Element, Comment, DocType} from 'hast' +``` + +or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: + +```js +/** + * @typedef {import("hast").Element} Element + * @typedef {import("hast").Comment} Comment + * @typedef {import("hast").DocType} DocType + */ +``` + +#### XAST (XML) + +The [E**x**tensible **A**bstract **S**yntax **T**ree (XAST)](https://github.com/syntax-tree/xast#readme) extends `unist` with types specific for XML such as [`Element`](https://github.com/syntax-tree/xast#element), [`CData`](https://github.com/syntax-tree/xast#cdata), [`Instruction`](https://github.com/syntax-tree/xast#instruction), and many more. +A full list of node types can be found in the [XAST documentation](https://github.com/syntax-tree/xast#readme). +[Typings for XAST are available on npm](https://www.npmjs.com/package/@types/xast) can be installed with a package manager. + +```bash +npm install --save-dev @types/xast +``` + +then import the types into a TypeScript file with: + +```ts +import type {Element, CData, Instruction} from 'xast' +``` + +or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: + +```js +/** + * @typedef {import("xast").Element} Element + * @typedef {import("xast").CData} CData + * @typedef {import("xast").Instruction} Instruction + */ +``` + +### How to traverse a syntax tree + +Once you have a syntax tree, often through using a parser such as `remark` for MDAST or `rehype` for HAST. +You want to be able to do something with that tree, often that includes finding specific types of content, then changing the content, validating or linting the content, or transforming the content. +To do this we need to be able to traverse the syntax tree looking for content. +Unified provides several type safe utilities which can help with this. + +#### `unist-util-visit` + +[`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), and a callback. +The callback will be called for each `Node` in the tree that passes the `Test`. + +For example if we want to increasing the heading level of all headings in a Markdown document: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import type {Heading} from 'mdast' +import {visit} from 'unist-util-visit' + +const markdownFile = await remark() + .use(() => (mdast: Node) => { + visit( + mdast, + // checks that the Node is a heading + 'heading', + (node: Heading) => { + node.depth += 1 + } + ) + }) + .process('## Hello, *World*!') + +console.log(markdownFile.toString()) +``` + +Or if we want to make all ordered lists in a Markdown document unordered: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import type {List} from 'mdast' +import {visit} from 'unist-util-visit' + +const markdownFile = await remark() + .use(() => (mdast: Node) => { + visit( + mdast, + // this checks both that the Node is a list and that it is ordered + {type: 'list', ordered: true}, + (node: List) => { + node.ordered = false + } + ) + }) + .process('1. list item') + +console.log(markdownFile.toString()) +``` + +Or we could want on all links which are not HTTPS: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import type {Link} from 'mdast' +import {is} from 'unist-util-is' +import {visit} from 'unist-util-visit' + +remark() + .use(() => (mdast: Node) => { + visit( + mdast, + // this both checks the node is a link and checks the content of url + (node: Node): node is Link => is(node, 'link') && !node.url.includes('https'), + (node: Link) => { + console.warn('link is not https', node.url) + } + ) + }) + .process('[link](http://example.com)') +``` + +#### `unist-util-visit-parents` + +Sometimes in addition to wanting to find a `Node` you also need to know the `Node`s higher in the tree, it's parents. +[`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) is similar to `unist-util-visit`, but also includes a list of parent nodes. + +For example if we want to check if all Markdown `ListItem` are inside a `List` we could: + +```ts +import remark from 'remark' +import type {Node, Parent} from 'unist' +import type {ListItem} from 'mdast' +import {visitParents} from 'unist-util-visit-parents' + +remark() + .use(() => (mdast: Node) => { + visitParents(mdast, 'listItem', (listItem: ListItem, parents: Parent[]) => { + if (!parents.some((parent) => parent.type === 'list')) { + console.warn('listItem is outside a list') + } + }) + }) + .process('1. list item') +``` + +#### `unist-util-select` + +In some cases it can be useful to find a `Node` relative to nearby `Nodes`. +[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) let's us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) to find `Node`s in a syntax tree. +For example in Markdown if we want to find all the `Paragraph`s inside `BlockQuote`s, we could: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import {selectAll} from 'unist-util-select' + +remark() + .use(() => (mdast: Node) => { + const matches = selectAll('blockquote paragraph', mdast) + console.log(matches) + }) + .process('1. list item') +``` + +### How to narrow generic `Node` to specific syntax types + +Unified works with many languages, and can pull content from strings, from files, and from virtual files. +In many cases unified doesn't know what specific `type` a `Node` will be ahead of time. +To work with a specific `Node` `type` or a small set of `Node` `types` we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type, taking the more general `Node` and doing type safe checks to get to a more specific type like a `Link`. +Unified provides several utilities to help with this, and there are some TypeScript language features which can also help. Let's take a look at `unist-util-is`. + +[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) and returns `true` if the test passes, and `false` if it does not. It also is a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) meaning if it is used as the condition for an `if` statement, TypeScript knows more about the type inside the `if`. + +For example: + +```ts +import type {Node, Literal} from 'unist' +import type {List, Blockquote, Strong, Emphasis, Heading} from 'mdast' +import {is, convert} from 'unist-util-is' + +// `Node` could come from a plugin, a utility, or be passed into a function +// here we hard code a Node for testing purposes +const node: Node = {type: 'example'} + +if (is(node, 'list')) { + // if we get here, node is List +} + +if (is(node, ['strong', 'emphasis'])) { + // if we get here, node is Strong or Emphasis + + // if we want even more specific type, we can use a discriminated union + // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions + if (node.type === 'emphasis') { + // if we get here, node is Emphasis + } +} + +if (is(node, {type: 'heading', depth: 1})) { + // if we get here, node is Heading +} + +// for advanced use cases, another predicate can be passed to `is` +if (is(node, (node: Node): node is Literal => "value" in node)) { + // if we get here, node is one of the Literal types +} + +// reusable predicates can also be created using any `Test` +const isBlockquote = convert('blockquote') +if (isBlockquote(node)) { + // if we get here, node is Blockquote +} +``` + +### How to build syntax tree + +When content needs to be created or added, it's often useful to build new syntax trees, or fragments of syntax trees. +This can be easy to do with plain JSON, unified also offers some utilities for building trees with hyperscript or JSX. + +### JSON + +In many cases a tree can be created with plain JSON, for example: + +```ts +const mdast = { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'example' + } + ] + } + ] +} +``` + +for some extra type safety this can be checked with the types for the given syntax tree language, in this case `mdast`: + +```ts +import type {Root} from 'mdast' + +const mdast: Root = { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'example' + } + ] + } + ] +} +``` + +#### `unist-util-builder` + +For more concise, hyperscript (or [`React.createElement`](https://reactjs.org/docs/react-api.html#createelement)) like syntax, [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme) can be use: + +```ts +import {u} from 'unist-builder' + +const mdast = u('root', [ + u('paragraph', [ + u('text', 'example') + ]) +]) +``` + +#### `hastscript` + +For working with an HTML AST (HAST) it can be more familiar to work with [JSX](https://reactjs.org/docs/introducing-jsx.html), [`hastscript`](https://github.com/syntax-tree/hastscript#readme) provide familiar syntax: + +```tsx +/* @jsxRuntime automatic */ +/* @jsxImportSource hastscript */ +/* @jsxFrag null */ + +console.log( +
+ some text + + + deltaecho + +
+) + +console.log( +
+ + + +
+) +``` + +#### `xastscript` + +Similarly for an XML AST (XAST). [`xastscript`](https://github.com/syntax-tree/xastscript#readme) provide familiar syntax: + +```tsx +/* @jsxRuntime automatic */ +/* @jsxImportSource xastscript */ +/* @jsxFrag null */ + +console.log( + + Born in the U.S.A. + Bruce Springsteen + 1984-04-06 + +) +``` From 78fe3f97b69503af02154e7079d28fd1d0ba3f51 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sat, 10 Jul 2021 06:50:27 -0700 Subject: [PATCH 02/14] style: format document --- doc/learn/syntax-trees-with-typescript.md | 99 +++++++++++------------ 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 490cc3960e0..e8c01f577df 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -14,41 +14,36 @@ modified: 2020-06-09 This guide will introduce you to: -* [The basic syntax tree types](#the-basics) - * [`Node`](#node) - * [`Literal`](#literal) - * [`Parent`](#parent) -* [How to include syntax tree typings for a specific language](#the-languages) - * [MDAST (Markdown)](#mdast-markdown) - * [HAST (HTML)](#hast-html) - * [XAST (XML)](#xast-xml) -* [How to traverse a syntax tree](#how-to-traverse-a-syntax-tree) - * [`unist-util-visit`](#unist-util-visit) - * [`unist-util-visit-parents`](#unist-util-visit-parents) - * [`unist-util-select`](#unist-util-select) -* [How to narrow generic `Node`s to specific syntax types](#how-to-narrow-generic-node-to-specific-syntax-types) -* [How to build a syntax tree](#how-to-build-syntax-tree) - * [JSON](#json) - * [`unist-util-builder`](#unist-util-builder) - * [`hastscript`](#hastscript) - * [`xastscript`](#xastscript) - -### The Basics +### Contents + +* [The basic syntax tree types](#the-basic-syntax-tree-types) +* [The Languages](#the-languages) +* [How to traverse a syntax tree](#how-to-traverse-a-syntax-tree) +* [How to narrow generic `Node` to specific syntax types](#how-to-narrow-generic-node-to-specific-syntax-types) +* [How to build syntax tree](#how-to-build-syntax-tree) + +### The basic syntax tree types All unified syntax trees are based off [the **Uni**versal **S**yntax **T**ree (`unist`)](https://github.com/syntax-tree/unist). -Unist is a types only package available on npm at [`@types/unist`](https://www.npmjs.com/package/@types/unist), and provides three interfaces which the rest of unified's syntax trees build on: `Node`, `Literal`, and `Parent`. +Unist is a types only package available on npm at [`@types/unist`](https://www.npmjs.com/package/@types/unist), +and provides three interfaces which the rest of unified's syntax trees build on: `Node`, `Literal`, and `Parent`. -#### Node +#### `Node` -Every unified extends `Node`, it is the syntactic unit of syntax trees. +Every unified extends `Node`, the syntactic unit of syntax trees. Every `Node` must have a `type`, the `type` tells us what kind of syntax the `Node` is. -For example in Markdown (mdast) `Node` will be extended to make different kinds of content such a `heading` or a `link`, a `paragraph` or a `blockquote` (among others). -Each of these different types extend `Node` (sometimes indirectly through `Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). +For example in Markdown (mdast) `Node` will be extended to make different kinds +of content such a `heading` or a `link`, a `paragraph` or a `blockquote` (among others). +Each of these different types extend `Node` (sometimes indirectly through +`Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) +which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). A `Node` also can optionally include `Data`. -`Data` is a dictionary/object which can store extra information and metadata which is not standard to a given node `type`. +`Data` is a dictionary/object which can store extra information and metadata +which is not standard to a given node `type`. -When a syntax tree is parsed from a file, it may include `Position`, which is information about where this `Node` is found in the source file. +When a syntax tree is parsed from a file, it may include `Position`, which is +information about where this `Node` is found in the source file. ```ts /** @@ -103,10 +98,10 @@ export interface Position { } ``` -#### Literal +#### `Literal` `Literal` extends `Node` and adds a `value` property. -For example a Markdown `text` `Node` extends `Literal` and sets `value` to be a `string`. +For example a markdown `text` `Node` extends `Literal` and sets `value` to be a `string`. ```ts /** @@ -117,8 +112,7 @@ export interface Literal extends Node { } ``` -#### Parent - +#### `Parent` ```ts /** @@ -134,11 +128,13 @@ export interface Parent extends Node { #### Pulling unist into a project +Install: + ```bash npm install --save-dev @types/unist ``` -then import the types into a TypeScript file with: +To import the types into a TypeScript file, use: ```ts import type {Node, Literal, Parent} from 'unist' @@ -157,9 +153,9 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- ### The Languages `unist` provides the building blocks for having consistent structure, but is often more generic than what we as developers and content authors want to work with. -We want to work with a specific language like Markdown, HTML, XML, and others. -Each of these languages has it's own syntax tree standard which extends `unist` with more specific content types. -Let's take a look at a few of Unified's compatible languages. +We want to work with a specific language like markdown, HTML, XML, and others. +Each of these languages has it’s own syntax tree standard which extends `unist` with more specific content types. +Let’s take a look at a few of unified’s compatible languages. #### MDAST (Markdown) @@ -167,17 +163,19 @@ The [**M**arkdown **A**bstract **S**yntax **T**ree (MDAST)](https://github.com/s A full list of node types can be found in the [MDAST documentation](https://github.com/syntax-tree/mdast#readme). [Typings for MDAST are available on npm](https://www.npmjs.com/package/@types/mdast) can be installed with a package manager. +Install: + ```bash npm install --save-dev @types/mdast ``` -then import the types into a TypeScript file with: +To import the types into a TypeScript file, use: ```ts import type {Heading, Code, Link} from 'mdast' ``` -or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: +To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html), use: ```js /** @@ -251,7 +249,7 @@ Unified provides several type safe utilities which can help with this. [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), and a callback. The callback will be called for each `Node` in the tree that passes the `Test`. -For example if we want to increasing the heading level of all headings in a Markdown document: +For example if we want to increasing the heading level of all headings in a markdown document: ```ts import remark from 'remark' @@ -275,7 +273,7 @@ const markdownFile = await remark() console.log(markdownFile.toString()) ``` -Or if we want to make all ordered lists in a Markdown document unordered: +Or if we want to make all ordered lists in a markdown document unordered: ```ts import remark from 'remark' @@ -299,7 +297,7 @@ const markdownFile = await remark() console.log(markdownFile.toString()) ``` -Or we could want on all links which are not HTTPS: +Or we could warn each time we find a link which has a URL that does not use HTTPS: ```ts import remark from 'remark' @@ -324,10 +322,10 @@ remark() #### `unist-util-visit-parents` -Sometimes in addition to wanting to find a `Node` you also need to know the `Node`s higher in the tree, it's parents. +Sometimes in addition to wanting to find a `Node` you also need to know the `Node`s higher in the tree, it’s parents. [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) is similar to `unist-util-visit`, but also includes a list of parent nodes. -For example if we want to check if all Markdown `ListItem` are inside a `List` we could: +For example if we want to check if all markdown `ListItem` are inside a `List` we could: ```ts import remark from 'remark' @@ -348,9 +346,9 @@ remark() #### `unist-util-select` -In some cases it can be useful to find a `Node` relative to nearby `Nodes`. -[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) let's us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) to find `Node`s in a syntax tree. -For example in Markdown if we want to find all the `Paragraph`s inside `BlockQuote`s, we could: +In some cases it can be useful to find a `Node` relative to nearby `Node`s. +[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) let’s us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) to find `Node`s in a syntax tree. +For example in markdown if we want to find all the `Paragraph`s inside `BlockQuote`s, we could: ```ts import remark from 'remark' @@ -368,11 +366,10 @@ remark() ### How to narrow generic `Node` to specific syntax types Unified works with many languages, and can pull content from strings, from files, and from virtual files. -In many cases unified doesn't know what specific `type` a `Node` will be ahead of time. To work with a specific `Node` `type` or a small set of `Node` `types` we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type, taking the more general `Node` and doing type safe checks to get to a more specific type like a `Link`. Unified provides several utilities to help with this, and there are some TypeScript language features which can also help. Let's take a look at `unist-util-is`. -[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) and returns `true` if the test passes, and `false` if it does not. It also is a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) meaning if it is used as the condition for an `if` statement, TypeScript knows more about the type inside the `if`. +[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) and returns `true` if the test passes, and `false` if it does not. It also is a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) meaning if used as the condition for an `if` statement, TypeScript knows more about the type inside the `if`. For example: @@ -417,12 +414,12 @@ if (isBlockquote(node)) { ### How to build syntax tree -When content needs to be created or added, it's often useful to build new syntax trees, or fragments of syntax trees. -This can be easy to do with plain JSON, unified also offers some utilities for building trees with hyperscript or JSX. +When content needs to be created or added, it’s often useful to build new syntax trees, or fragments of syntax trees. +This can be easy to do with plain JSON, unified also offers some utilities for building trees with `hyperscript` or `JSX`. -### JSON +#### JSON -In many cases a tree can be created with plain JSON, for example: +Often a tree can be created with plain JSON, for example: ```ts const mdast = { From c276940d0dfef6b806568b890a28c1953d14dee7 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sat, 10 Jul 2021 07:27:39 -0700 Subject: [PATCH 03/14] docs: add summary, improve prose --- doc/learn/syntax-trees-with-typescript.md | 146 ++++++++++++++-------- 1 file changed, 96 insertions(+), 50 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index e8c01f577df..2de9e39dd3e 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -21,21 +21,23 @@ This guide will introduce you to: * [How to traverse a syntax tree](#how-to-traverse-a-syntax-tree) * [How to narrow generic `Node` to specific syntax types](#how-to-narrow-generic-node-to-specific-syntax-types) * [How to build syntax tree](#how-to-build-syntax-tree) +* [Summary](#summary) ### The basic syntax tree types All unified syntax trees are based off [the **Uni**versal **S**yntax **T**ree (`unist`)](https://github.com/syntax-tree/unist). Unist is a types only package available on npm at [`@types/unist`](https://www.npmjs.com/package/@types/unist), -and provides three interfaces which the rest of unified's syntax trees build on: `Node`, `Literal`, and `Parent`. +and provides three interfaces which the rest of unified’s syntax trees +build on: `Node`, `Literal`, and `Parent`. #### `Node` Every unified extends `Node`, the syntactic unit of syntax trees. Every `Node` must have a `type`, the `type` tells us what kind of syntax the `Node` is. -For example in Markdown (mdast) `Node` will be extended to make different kinds -of content such a `heading` or a `link`, a `paragraph` or a `blockquote` (among others). -Each of these different types extend `Node` (sometimes indirectly through -`Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) +For example in Markdown (mdast) `Node` will be extended to make different +kinds of content such a `heading` or a `link`, a `paragraph` or a `blockquote` +(among others). Each of these different types extend `Node` (sometimes +indirectly through `Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). A `Node` also can optionally include `Data`. @@ -43,7 +45,7 @@ A `Node` also can optionally include `Data`. which is not standard to a given node `type`. When a syntax tree is parsed from a file, it may include `Position`, which is -information about where this `Node` is found in the source file. +information about the `Node`’s location in source file. ```ts /** @@ -152,15 +154,19 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- ### The Languages -`unist` provides the building blocks for having consistent structure, but is often more generic than what we as developers and content authors want to work with. -We want to work with a specific language like markdown, HTML, XML, and others. -Each of these languages has it’s own syntax tree standard which extends `unist` with more specific content types. -Let’s take a look at a few of unified’s compatible languages. +`unist` provides the building blocks for having consistent structure, but is +often more generic than what we as developers and content authors want to work +with. We want to work with a specific language like markdown, HTML, XML, and +others. Each of these languages has it’s own syntax tree standard which extends +`unist` with more specific content types. Let’s take a look at a few of +unified’s compatible languages. #### MDAST (Markdown) -The [**M**arkdown **A**bstract **S**yntax **T**ree (MDAST)](https://github.com/syntax-tree/mdast#readme) extends `unist` with types specific for Markdown such as [`Heading`](https://github.com/syntax-tree/mdast#heading), [`Code`](https://github.com/syntax-tree/mdast#code), [`Link`](https://github.com/syntax-tree/mdast#link), and many more. -A full list of node types can be found in the [MDAST documentation](https://github.com/syntax-tree/mdast#readme). +The [**M**arkdown **A**bstract **S**yntax **T**ree (MDAST)](https://github.com/syntax-tree/mdast#readme) +extends `unist` with types specific for Markdown such as [`Heading`](https://github.com/syntax-tree/mdast#heading), +[`Code`](https://github.com/syntax-tree/mdast#code), [`Link`](https://github.com/syntax-tree/mdast#link), +and many more. A full list of node types can be found in the [MDAST documentation](https://github.com/syntax-tree/mdast#readme). [Typings for MDAST are available on npm](https://www.npmjs.com/package/@types/mdast) can be installed with a package manager. Install: @@ -191,17 +197,19 @@ The [**H**ypertext **A**bstract **S**yntax **T**ree (HAST)](https://github.com/s A full list of node types can be found in the [HAST documentation](https://github.com/syntax-tree/hast#readme). [Typings for HAST are available on npm](https://www.npmjs.com/package/@types/hast) can be installed with a package manager. +Install: + ```bash npm install --save-dev @types/hast ``` -then import the types into a TypeScript file with: +To import the types into a TypeScript file, use: ```ts import type {Element, Comment, DocType} from 'hast' ``` -or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: +To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html), use: ```js /** @@ -213,21 +221,25 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- #### XAST (XML) -The [E**x**tensible **A**bstract **S**yntax **T**ree (XAST)](https://github.com/syntax-tree/xast#readme) extends `unist` with types specific for XML such as [`Element`](https://github.com/syntax-tree/xast#element), [`CData`](https://github.com/syntax-tree/xast#cdata), [`Instruction`](https://github.com/syntax-tree/xast#instruction), and many more. -A full list of node types can be found in the [XAST documentation](https://github.com/syntax-tree/xast#readme). +The [E**x**tensible **A**bstract **S**yntax **T**ree (XAST)](https://github.com/syntax-tree/xast#readme) +extends `unist` with types specific for XML such as [`Element`](https://github.com/syntax-tree/xast#element), +[`CData`](https://github.com/syntax-tree/xast#cdata), [`Instruction`](https://github.com/syntax-tree/xast#instruction), +and many more. A full list of node types can be found in the [XAST documentation](https://github.com/syntax-tree/xast#readme). [Typings for XAST are available on npm](https://www.npmjs.com/package/@types/xast) can be installed with a package manager. +Install: + ```bash npm install --save-dev @types/xast ``` -then import the types into a TypeScript file with: +To import the types into a TypeScript file, use: ```ts import type {Element, CData, Instruction} from 'xast' ``` -or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) project with: +To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html), use: ```js /** @@ -239,17 +251,25 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- ### How to traverse a syntax tree -Once you have a syntax tree, often through using a parser such as `remark` for MDAST or `rehype` for HAST. -You want to be able to do something with that tree, often that includes finding specific types of content, then changing the content, validating or linting the content, or transforming the content. +:notebook: consider reading the [introduction to tree traversal in JavaScript](./tree-traversal) +before reading this section. + +Once you have a syntax tree, often through using a parser such as `remark` for +MDAST or `rehype` for HAST. You want to be able to do something with that tree, +often that includes finding specific types of content, then changing the +content, validating or linting the content, or transforming the content. To do this we need to be able to traverse the syntax tree looking for content. -Unified provides several type safe utilities which can help with this. +Unified provides several type-safe utilities which can help with this. #### `unist-util-visit` -[`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), and a callback. -The callback will be called for each `Node` in the tree that passes the `Test`. +[`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) +takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), +and a callback. The callback will be called for each `Node` in the tree that +passes the `Test`. -For example if we want to increasing the heading level of all headings in a markdown document: +For example if we want to increasing the heading level of all headings in a +markdown document: ```ts import remark from 'remark' @@ -297,7 +317,8 @@ const markdownFile = await remark() console.log(markdownFile.toString()) ``` -Or we could warn each time we find a link which has a URL that does not use HTTPS: +Or we could warn each time we find a link which has a URL that does not use +HTTPS: ```ts import remark from 'remark' @@ -322,10 +343,12 @@ remark() #### `unist-util-visit-parents` -Sometimes in addition to wanting to find a `Node` you also need to know the `Node`s higher in the tree, it’s parents. -[`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) is similar to `unist-util-visit`, but also includes a list of parent nodes. +Sometimes in addition to wanting to find a `Node` you also need to know the +`Node`s higher in the tree, its parents. [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) +is similar to `unist-util-visit`, but also includes a list of parent nodes. -For example if we want to check if all markdown `ListItem` are inside a `List` we could: +For example if we want to check if all markdown `ListItem` are inside a `List` +we could: ```ts import remark from 'remark' @@ -346,9 +369,11 @@ remark() #### `unist-util-select` -In some cases it can be useful to find a `Node` relative to nearby `Node`s. -[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) let’s us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) to find `Node`s in a syntax tree. -For example in markdown if we want to find all the `Paragraph`s inside `BlockQuote`s, we could: +In some cases it can be useful to find a `Node` close to another `Node`s. +[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) lets +us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) +to find `Node`s in a syntax tree. For example in markdown if we want to find +all the `Paragraph`s inside `Blockquote`s, we could: ```ts import remark from 'remark' @@ -365,11 +390,20 @@ remark() ### How to narrow generic `Node` to specific syntax types -Unified works with many languages, and can pull content from strings, from files, and from virtual files. -To work with a specific `Node` `type` or a small set of `Node` `types` we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type, taking the more general `Node` and doing type safe checks to get to a more specific type like a `Link`. -Unified provides several utilities to help with this, and there are some TypeScript language features which can also help. Let's take a look at `unist-util-is`. - -[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) and returns `true` if the test passes, and `false` if it does not. It also is a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) meaning if used as the condition for an `if` statement, TypeScript knows more about the type inside the `if`. +Unified works with many languages, and can pull content from strings, from +files, and from virtual files. To work with a specific `Node` `type` or a small +set of `Node` `types` we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) +the type, taking the more general `Node` and doing type safe checks to get to a +more specific type like a `Link`. Unified provides several utilities to help +with this, and there are some TypeScript language features which can also help. +Let's take a look at `unist-util-is`. + +[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a +`Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) +and returns `true` if the test passes, and `false` if it does not. It also is +a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) +meaning if used as the condition for an `if` statement, TypeScript knows more +about the type inside the `if`. For example: @@ -383,39 +417,41 @@ import {is, convert} from 'unist-util-is' const node: Node = {type: 'example'} if (is(node, 'list')) { - // if we get here, node is List + // If we get here, node is List } if (is(node, ['strong', 'emphasis'])) { - // if we get here, node is Strong or Emphasis + // If we get here, node is Strong or Emphasis - // if we want even more specific type, we can use a discriminated union + // If we want even more specific type, we can use a discriminated union // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions if (node.type === 'emphasis') { - // if we get here, node is Emphasis + // If we get here, node is Emphasis } } if (is(node, {type: 'heading', depth: 1})) { - // if we get here, node is Heading + // If we get here, node is Heading } -// for advanced use cases, another predicate can be passed to `is` -if (is(node, (node: Node): node is Literal => "value" in node)) { - // if we get here, node is one of the Literal types +// For advanced use cases, another predicate can be passed to `is` +if (is(node, (node: Node): node is Literal => 'value' in node)) { + // If we get here, node is one of the Literal types } -// reusable predicates can also be created using any `Test` -const isBlockquote = convert('blockquote') +// Reusable predicates can also be created using any `Test` +const isBlockquote = convert
('blockquote') if (isBlockquote(node)) { - // if we get here, node is Blockquote + // If we get here, node is Blockquote } ``` ### How to build syntax tree -When content needs to be created or added, it’s often useful to build new syntax trees, or fragments of syntax trees. -This can be easy to do with plain JSON, unified also offers some utilities for building trees with `hyperscript` or `JSX`. +When content needs to be created or added, it’s often useful to build new +syntax trees, or fragments of syntax trees. This can be easy to do with plain +JSON, unified also offers some utilities for building trees with `hyperscript` +or `JSX`. #### JSON @@ -461,7 +497,9 @@ const mdast: Root = { #### `unist-util-builder` -For more concise, hyperscript (or [`React.createElement`](https://reactjs.org/docs/react-api.html#createelement)) like syntax, [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme) can be use: +For more concise, hyperscript (or [`React.createElement`](https://reactjs.org/docs/react-api.html#createelement)) +like syntax, [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme) +can be used: ```ts import {u} from 'unist-builder' @@ -518,3 +556,11 @@ console.log( ) ``` + +### Summary + +* Using TypeScript can make finding typos and bugs easier +* Unified provides types for each language’s syntax tree and utilities to + work with these types +* Types are available for most plugins and utilities (and if types haven’t + been added a pull request is welcome!) From 0119321d81071b7c11a70b282ca16460908f9caa Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sat, 10 Jul 2021 07:38:26 -0700 Subject: [PATCH 04/14] style: format snippets from definitely typed --- doc/learn/syntax-trees-with-typescript.md | 73 ++++++++++++----------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 2de9e39dd3e..c03216d9a35 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -52,21 +52,21 @@ information about the `Node`’s location in source file. * Syntactic units in unist syntax trees are called nodes. */ interface Node { - /** - * The variant of a node. - */ - type: string; - - /** - * Information from the ecosystem. - */ - data?: Data | undefined; - - /** - * Location of a node in a source document. - * Must not be present if a node is generated. - */ - position?: Position | undefined; + /** + * The variant of a node. + */ + type: string + + /** + * Information from the ecosystem. + */ + data?: Data | undefined + + /** + * Location of a node in a source document. + * Must not be present if a node is generated. + */ + position?: Position | undefined } /** @@ -75,29 +75,30 @@ interface Node { * implementing unist. */ export interface Data { - [key: string]: unknown; + [key: string]: unknown } /** * Location of a node in a source file. */ export interface Position { - /** - * Place of the first character of the parsed source region. - */ - start: Point; - - /** - * Place of the first character after the parsed source region. - */ - end: Point; - - /** - * Start column at each index (plus start line) in the source region, - * for elements that span multiple lines. - */ - indent?: number[] | undefined; + /** + * Place of the first character of the parsed source region. + */ + start: Point + + /** + * Place of the first character after the parsed source region. + */ + end: Point + + /** + * Start column at each index (plus start line) in the source region, + * for elements that span multiple lines. + */ + indent?: number[] | undefined } + ``` #### `Literal` @@ -110,7 +111,7 @@ For example a markdown `text` `Node` extends `Literal` and sets `value` to be a * Nodes containing a value. */ export interface Literal extends Node { - value: unknown; + value: unknown } ``` @@ -121,10 +122,10 @@ export interface Literal extends Node { * Nodes containing other nodes. */ export interface Parent extends Node { - /** - * List representing the children of a node. - */ - children: Node[]; + /** + * List representing the children of a node. + */ + children: Node[]; } ``` From b4423ee77650bc55db6f087d6e2db75c2dbfe112 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sat, 10 Jul 2021 09:01:59 -0700 Subject: [PATCH 05/14] style: reduce use of inline code referring to Node, use single quotes in jsdoc, limit line length --- doc/learn/syntax-trees-with-typescript.md | 67 ++++++++++++----------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index c03216d9a35..d34e7a0405f 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -33,19 +33,19 @@ build on: `Node`, `Literal`, and `Parent`. #### `Node` Every unified extends `Node`, the syntactic unit of syntax trees. -Every `Node` must have a `type`, the `type` tells us what kind of syntax the `Node` is. -For example in Markdown (mdast) `Node` will be extended to make different -kinds of content such a `heading` or a `link`, a `paragraph` or a `blockquote` +Every `Node` must have a `type`, the type tells us what kind of syntax the node +is. For example in Markdown (mdast) `Node` will be extended to make different +kinds of content such a `Heading` or a `Link`, a `Paragraph` or a `Blockquote` (among others). Each of these different types extend `Node` (sometimes indirectly through `Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). -A `Node` also can optionally include `Data`. -`Data` is a dictionary/object which can store extra information and metadata -which is not standard to a given node `type`. +A node also can optionally include `Data`. +Data is a dictionary/object which can store extra information and metadata +which is not standard to a given node type. When a syntax tree is parsed from a file, it may include `Position`, which is -information about the `Node`’s location in source file. +information about the Node’s location in source file. ```ts /** @@ -104,7 +104,7 @@ export interface Position { #### `Literal` `Literal` extends `Node` and adds a `value` property. -For example a markdown `text` `Node` extends `Literal` and sets `value` to be a `string`. +For example a markdown `text` node extends `Literal` and sets `value` to be a `string`. ```ts /** @@ -117,6 +117,9 @@ export interface Literal extends Node { #### `Parent` +`Parent` extends `Node` and adds `children`. +Children represent other content which is inside or a part of this node. + ```ts /** * Nodes containing other nodes. @@ -147,9 +150,9 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- ```js /** - * @typedef {import("unist").Node} Node - * @typedef {import("unist").Literal} Literal - * @typedef {import("unist").Parent} Parent + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Literal} Literal + * @typedef {import('unist').Parent} Parent */ ``` @@ -186,9 +189,9 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha ```js /** - * @typedef {import("mdast").Heading} Heading - * @typedef {import("mdast").Code} Code - * @typedef {import("mdast").Link} Link + * @typedef {import('mdast').Heading} Heading + * @typedef {import('mdast').Code} Code + * @typedef {import('mdast').Link} Link */ ``` @@ -214,9 +217,9 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha ```js /** - * @typedef {import("hast").Element} Element - * @typedef {import("hast").Comment} Comment - * @typedef {import("hast").DocType} DocType + * @typedef {import('hast').Element} Element + * @typedef {import('hast').Comment} Comment + * @typedef {import('hast').DocType} DocType */ ``` @@ -226,7 +229,8 @@ The [E**x**tensible **A**bstract **S**yntax **T**ree (XAST)](https://github.com/ extends `unist` with types specific for XML such as [`Element`](https://github.com/syntax-tree/xast#element), [`CData`](https://github.com/syntax-tree/xast#cdata), [`Instruction`](https://github.com/syntax-tree/xast#instruction), and many more. A full list of node types can be found in the [XAST documentation](https://github.com/syntax-tree/xast#readme). -[Typings for XAST are available on npm](https://www.npmjs.com/package/@types/xast) can be installed with a package manager. +[Typings for XAST are available on npm](https://www.npmjs.com/package/@types/xast) +can be installed with a package manager. Install: @@ -244,9 +248,9 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha ```js /** - * @typedef {import("xast").Element} Element - * @typedef {import("xast").CData} CData - * @typedef {import("xast").Instruction} Instruction + * @typedef {import('xast').Element} Element + * @typedef {import('xast').CData} CData + * @typedef {import('xast').Instruction} Instruction */ ``` @@ -266,7 +270,7 @@ Unified provides several type-safe utilities which can help with this. [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), -and a callback. The callback will be called for each `Node` in the tree that +and a callback. The callback will be called for each node in the tree that passes the `Test`. For example if we want to increasing the heading level of all headings in a @@ -344,9 +348,9 @@ remark() #### `unist-util-visit-parents` -Sometimes in addition to wanting to find a `Node` you also need to know the -`Node`s higher in the tree, its parents. [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) -is similar to `unist-util-visit`, but also includes a list of parent nodes. +Sometimes besides to wanting to find a node you also need to know the +Node’s higher in the tree, its parents. [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) +is like `unist-util-visit`, but also includes a list of parent nodes. For example if we want to check if all markdown `ListItem` are inside a `List` we could: @@ -392,12 +396,12 @@ remark() ### How to narrow generic `Node` to specific syntax types Unified works with many languages, and can pull content from strings, from -files, and from virtual files. To work with a specific `Node` `type` or a small -set of `Node` `types` we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) +files, and from virtual files. To work with a specific node type or a small +set of node types we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type, taking the more general `Node` and doing type safe checks to get to a more specific type like a `Link`. Unified provides several utilities to help with this, and there are some TypeScript language features which can also help. -Let's take a look at `unist-util-is`. +Let’s take a look at `unist-util-is`. [`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) @@ -451,8 +455,8 @@ if (isBlockquote(node)) { When content needs to be created or added, it’s often useful to build new syntax trees, or fragments of syntax trees. This can be easy to do with plain -JSON, unified also offers some utilities for building trees with `hyperscript` -or `JSX`. +JSON, unified also offers some utilities for building trees with hyperscript +or JSX. #### JSON @@ -475,7 +479,8 @@ const mdast = { } ``` -for some extra type safety this can be checked with the types for the given syntax tree language, in this case `mdast`: +for some extra type safety this can be checked with the types for the given +syntax tree language, in this case MDAST: ```ts import type {Root} from 'mdast' From 0781f7aa805a068c6f6ef82065352a98067c2cd8 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sun, 11 Jul 2021 11:31:11 -0700 Subject: [PATCH 06/14] docs: code review suggestions Co-authored-by: Titus --- doc/learn/syntax-trees-with-typescript.md | 201 ++++++++++++---------- 1 file changed, 112 insertions(+), 89 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index d34e7a0405f..58cf7365639 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -12,7 +12,7 @@ modified: 2020-06-09 ## Working with syntax trees in TypeScript -This guide will introduce you to: +This guide will introduce you to using unist and unified with TypeScript. ### Contents @@ -25,27 +25,31 @@ This guide will introduce you to: ### The basic syntax tree types -All unified syntax trees are based off [the **Uni**versal **S**yntax **T**ree (`unist`)](https://github.com/syntax-tree/unist). -Unist is a types only package available on npm at [`@types/unist`](https://www.npmjs.com/package/@types/unist), -and provides three interfaces which the rest of unified’s syntax trees -build on: `Node`, `Literal`, and `Parent`. +All unified syntax trees are based off [unist (**uni**versal **s**yntax **t**ree)](https://github.com/syntax-tree/unist). +The core types are available in a types only package: [`@types/unist`](https://www.npmjs.com/package/@types/unist). +The main type is `Node`. +Everything else extends it. +`Literal` and `Parent` are more practical types which also extend `Node`. #### `Node` -Every unified extends `Node`, the syntactic unit of syntax trees. -Every `Node` must have a `type`, the type tells us what kind of syntax the node -is. For example in Markdown (mdast) `Node` will be extended to make different -kinds of content such a `Heading` or a `Link`, a `Paragraph` or a `Blockquote` -(among others). Each of these different types extend `Node` (sometimes -indirectly through `Literal` or `Parent`) and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) -which uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). +`Node` is the syntactic unit of syntax trees. +Each node extends `Node` (sometimes indirectly through `Literal` or `Parent`) +and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) +The type field tells us what kind of syntax the node is. +This field uniquely identifies a kind of content (in TypeScript parlance a +[discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). +For example in Markdown (mdast) `Node` will be extended to make different kinds +of content such as a `Heading` or `Link`, which respectively use a `type` field +of `'heading'` and `'link'`. -A node also can optionally include `Data`. -Data is a dictionary/object which can store extra information and metadata -which is not standard to a given node type. +A node also can optionally include a `Data` interface at the `data` field. +This is an object (dictionary) that stores extra metadata which is not standard +to the node but defined by the ecosystem (utilities and plugins). -When a syntax tree is parsed from a file, it may include `Position`, which is -information about the Node’s location in source file. +When a syntax tree is parsed from a file, it includes positional information: +a `Position` interface at the `position` field. +This describes where the node occurred in the source file. ```ts /** @@ -104,7 +108,7 @@ export interface Position { #### `Literal` `Literal` extends `Node` and adds a `value` property. -For example a markdown `text` node extends `Literal` and sets `value` to be a `string`. +For example a markdown `code` node extends `Literal` and sets `value` to be a `string`. ```ts /** @@ -158,20 +162,20 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- ### The Languages -`unist` provides the building blocks for having consistent structure, but is -often more generic than what we as developers and content authors want to work -with. We want to work with a specific language like markdown, HTML, XML, and -others. Each of these languages has it’s own syntax tree standard which extends -`unist` with more specific content types. Let’s take a look at a few of -unified’s compatible languages. +The types provided by unist are abstract interfaces. +In many cases, you will instead use more specific interfaces depending on what +language you’re working with. +Each language supported by unified, like markdown, HTML, and XML, has its own +syntax tree standard which extends `unist`. +Let’s take a look at a few of them. #### MDAST (Markdown) -The [**M**arkdown **A**bstract **S**yntax **T**ree (MDAST)](https://github.com/syntax-tree/mdast#readme) -extends `unist` with types specific for Markdown such as [`Heading`](https://github.com/syntax-tree/mdast#heading), -[`Code`](https://github.com/syntax-tree/mdast#code), [`Link`](https://github.com/syntax-tree/mdast#link), -and many more. A full list of node types can be found in the [MDAST documentation](https://github.com/syntax-tree/mdast#readme). -[Typings for MDAST are available on npm](https://www.npmjs.com/package/@types/mdast) can be installed with a package manager. +[mdast (**m**arkdown **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/mdast#readme) +extends unist with types specific for markdown such as `Heading`, `Code`, +`Link`, and many more. +A full list of nodes can be found in the [specification](https://github.com/syntax-tree/mdast#readme). +The types are available in a types only package: [`@types/mdast`](https://www.npmjs.com/package/@types/mdast). Install: @@ -197,9 +201,11 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha #### HAST (HTML) -The [**H**ypertext **A**bstract **S**yntax **T**ree (HAST)](https://github.com/syntax-tree/hast#readme) extends `unist` with types specific for HTML such as [`Element`](https://github.com/syntax-tree/hast#element), [`Comment`](https://github.com/syntax-tree/hast#comment), [`DocType`](https://github.com/syntax-tree/hast#doctype), and many more. -A full list of node types can be found in the [HAST documentation](https://github.com/syntax-tree/hast#readme). -[Typings for HAST are available on npm](https://www.npmjs.com/package/@types/hast) can be installed with a package manager. +[hast (**h**ypertext **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/hast#readme) +extends unist with types specific for HTML such as `Element`, `Comment`, +`DocType`, and many more. +A full list of nodes can be found in the [specification](https://github.com/syntax-tree/hast#readme). +The types are available in a types only package: [`@types/hast`](https://www.npmjs.com/package/@types/hast). Install: @@ -225,12 +231,11 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha #### XAST (XML) -The [E**x**tensible **A**bstract **S**yntax **T**ree (XAST)](https://github.com/syntax-tree/xast#readme) -extends `unist` with types specific for XML such as [`Element`](https://github.com/syntax-tree/xast#element), -[`CData`](https://github.com/syntax-tree/xast#cdata), [`Instruction`](https://github.com/syntax-tree/xast#instruction), -and many more. A full list of node types can be found in the [XAST documentation](https://github.com/syntax-tree/xast#readme). -[Typings for XAST are available on npm](https://www.npmjs.com/package/@types/xast) -can be installed with a package manager. +[xast (e**x**tensible **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/xast#readme) +extends unist with types specific for HTML such as `Element`, `CData`, +`Instruction`, and many more. +A full list of nodes can be found in the [specification](https://github.com/syntax-tree/xast#readme). +The types are available in a types only package: [`@types/xast`](https://www.npmjs.com/package/@types/xast). Install: @@ -256,22 +261,19 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha ### How to traverse a syntax tree -:notebook: consider reading the [introduction to tree traversal in JavaScript](./tree-traversal) +:notebook: please read the [introduction to tree traversal in JavaScript](./tree-traversal/) before reading this section. -Once you have a syntax tree, often through using a parser such as `remark` for -MDAST or `rehype` for HAST. You want to be able to do something with that tree, -often that includes finding specific types of content, then changing the -content, validating or linting the content, or transforming the content. -To do this we need to be able to traverse the syntax tree looking for content. -Unified provides several type-safe utilities which can help with this. +A frequent task when working with unified is to traverse trees to find certain +nodes and then doing something with them (often validating or transforming +them). +There are several type-safe utilities provided by unified to help with this. #### `unist-util-visit` [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) -takes a syntax tree, a [`Test`](https://github.com/syntax-tree/unist-util-is#use), -and a callback. The callback will be called for each node in the tree that -passes the `Test`. +takes a syntax tree, a `Test`, and a callback. +The callback is called for each node in the tree that passes `Test`. For example if we want to increasing the heading level of all headings in a markdown document: @@ -286,7 +288,7 @@ const markdownFile = await remark() .use(() => (mdast: Node) => { visit( mdast, - // checks that the Node is a heading + // Check that the Node is a heading: 'heading', (node: Heading) => { node.depth += 1 @@ -310,7 +312,7 @@ const markdownFile = await remark() .use(() => (mdast: Node) => { visit( mdast, - // this checks both that the Node is a list and that it is ordered + // Check that the Node is a list and that it is ordered: {type: 'list', ordered: true}, (node: List) => { node.ordered = false @@ -348,9 +350,9 @@ remark() #### `unist-util-visit-parents` -Sometimes besides to wanting to find a node you also need to know the -Node’s higher in the tree, its parents. [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) -is like `unist-util-visit`, but also includes a list of parent nodes. +Sometimes it’s needed to know the ancestors of a node (all its parents). +[`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) +is like `unist-util-visit` but includes a list of all parent nodes. For example if we want to check if all markdown `ListItem` are inside a `List` we could: @@ -374,11 +376,12 @@ remark() #### `unist-util-select` -In some cases it can be useful to find a `Node` close to another `Node`s. +Sometimes CSS selectors are easier to read than several (nested) if/else +statements. [`unist-util-select`](https://github.com/syntax-tree/unist-util-select) lets -us use [CSS selectors](https://github.com/syntax-tree/unist-util-select#support) -to find `Node`s in a syntax tree. For example in markdown if we want to find -all the `Paragraph`s inside `Blockquote`s, we could: +you do just that. +For example if we want to find all `Paragraph`s that are somewhere in a +`Blockquote`, we could: ```ts import remark from 'remark' @@ -395,13 +398,14 @@ remark() ### How to narrow generic `Node` to specific syntax types -Unified works with many languages, and can pull content from strings, from -files, and from virtual files. To work with a specific node type or a small -set of node types we need to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) -the type, taking the more general `Node` and doing type safe checks to get to a -more specific type like a `Link`. Unified provides several utilities to help -with this, and there are some TypeScript language features which can also help. -Let’s take a look at `unist-util-is`. +To work with a specific node type or a set of node types we need to +[narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) their +type. +For example, we can take a `Node` and perform a type safe check to get a more +specific type like a `Link`. +Unified provides a utility to help with this and there are some TypeScript +language features which can also help. +Let’s first take a look at `unist-util-is`. [`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a `Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) @@ -453,14 +457,15 @@ if (isBlockquote(node)) { ### How to build syntax tree -When content needs to be created or added, it’s often useful to build new -syntax trees, or fragments of syntax trees. This can be easy to do with plain -JSON, unified also offers some utilities for building trees with hyperscript -or JSX. +It’s often useful to build new (fragments of) syntax trees when adding or +replacing content. +It’s possible to create trees with plain object and array literals (JSON) or +programmatically with a small utility. +Finally it’s even possible to use JSX to build trees. #### JSON -Often a tree can be created with plain JSON, for example: +The most basic way to create a tree is with plain object and arrays, such as: ```ts const mdast = { @@ -501,11 +506,11 @@ const mdast: Root = { } ``` -#### `unist-util-builder` +#### `unist-builder` -For more concise, hyperscript (or [`React.createElement`](https://reactjs.org/docs/react-api.html#createelement)) -like syntax, [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme) -can be used: +It’s also possible to build trees with [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme). +It allows a more concise, hyperscript (similar to `React.createElement`) like +syntax: ```ts import {u} from 'unist-builder' @@ -519,22 +524,26 @@ const mdast = u('root', [ #### `hastscript` -For working with an HTML AST (HAST) it can be more familiar to work with [JSX](https://reactjs.org/docs/introducing-jsx.html), [`hastscript`](https://github.com/syntax-tree/hastscript#readme) provide familiar syntax: +When working with hast (HTML), [`hastscript`](https://github.com/syntax-tree/hastscript#readme) +can be used. -```tsx -/* @jsxRuntime automatic */ -/* @jsxImportSource hastscript */ -/* @jsxFrag null */ +```ts +import {h} from 'hastscript' console.log( -
- some text - - - deltaecho - -
+ h('div#some-id.foo', [ + h('span', 'some text'), + h('input', {type: 'text', value: 'foo'}), + h('a.alpha.bravo.charlie', {download: true}, 'delta') + ]) ) +``` + +hastscript can also be used as a JSX pragma: + +```tsx +/** @jsx h @jsxFrag null */ +import {h} from 'hastscript' console.log(
@@ -547,12 +556,26 @@ console.log( #### `xastscript` -Similarly for an XML AST (XAST). [`xastscript`](https://github.com/syntax-tree/xastscript#readme) provide familiar syntax: +When working with xast (XML), [`xastscript`](https://github.com/syntax-tree/xastscript#readme) +can be used. + +```ts +import {x} from 'xastscript' + +console.log( + x('album', {id: 123}, [ + x('name', 'Exile in Guyville'), + x('artist', 'Liz Phair'), + x('releasedate', '1993-06-22') + ]) +) +``` + +xastscript can also be used as a JSX pragma: ```tsx -/* @jsxRuntime automatic */ -/* @jsxImportSource xastscript */ -/* @jsxFrag null */ +/** @jsx x @jsxFrag null */ +import {x} from 'xastscript' console.log( From 063278d943c486e3d2213f5e42ba0d18d32271ba Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sun, 11 Jul 2021 13:41:41 -0700 Subject: [PATCH 07/14] docs: remove visit predicate example, it is covered by unist-util-is --- doc/learn/syntax-trees-with-typescript.md | 25 ----------------------- 1 file changed, 25 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 58cf7365639..0db0929c66d 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -324,30 +324,6 @@ const markdownFile = await remark() console.log(markdownFile.toString()) ``` -Or we could warn each time we find a link which has a URL that does not use -HTTPS: - -```ts -import remark from 'remark' -import type {Node} from 'unist' -import type {Link} from 'mdast' -import {is} from 'unist-util-is' -import {visit} from 'unist-util-visit' - -remark() - .use(() => (mdast: Node) => { - visit( - mdast, - // this both checks the node is a link and checks the content of url - (node: Node): node is Link => is(node, 'link') && !node.url.includes('https'), - (node: Link) => { - console.warn('link is not https', node.url) - } - ) - }) - .process('[link](http://example.com)') -``` - #### `unist-util-visit-parents` Sometimes it’s needed to know the ancestors of a node (all its parents). @@ -588,7 +564,6 @@ console.log( ### Summary -* Using TypeScript can make finding typos and bugs easier * Unified provides types for each language’s syntax tree and utilities to work with these types * Types are available for most plugins and utilities (and if types haven’t From 8e601cd9a8d3f824094e69e68ca43659b8728d24 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sun, 11 Jul 2021 14:29:50 -0700 Subject: [PATCH 08/14] docs: split guide into guide and three recipes --- doc/learn/build-a-syntax-tree-typescript.md | 130 +++++++ .../node-type-narrowing-in-typescript.md | 73 ++++ doc/learn/syntax-trees-with-typescript.md | 361 ++---------------- doc/learn/tree-traversal-typescript.md | 129 +++++++ 4 files changed, 366 insertions(+), 327 deletions(-) create mode 100644 doc/learn/build-a-syntax-tree-typescript.md create mode 100644 doc/learn/node-type-narrowing-in-typescript.md create mode 100644 doc/learn/tree-traversal-typescript.md diff --git a/doc/learn/build-a-syntax-tree-typescript.md b/doc/learn/build-a-syntax-tree-typescript.md new file mode 100644 index 00000000000..55125791d31 --- /dev/null +++ b/doc/learn/build-a-syntax-tree-typescript.md @@ -0,0 +1,130 @@ +--- +group: recipe +index: 3 +title: Building a content syntax tree +description: How to build content with syntax trees +tags: + - mdast + - hast + - xast + - builder + - hyperscript + - jsx +author: Christian Murphy +authorGithub: ChristianMurphy +published: 2020-06-09 +modified: 2020-06-11 +--- + +## How to build syntax tree + +It’s often useful to build new (fragments of) syntax trees when adding or +replacing content. +It’s possible to create trees with plain object and array literals (JSON) or +programmatically with a small utility. +Finally it’s even possible to use JSX to build trees. + +### JSON + +The most basic way to create a tree is with plain object and arrays, for some +extra type safety this can be checked with the types for the given syntax tree +language, in this case MDAST: + +```ts +import type {Root} from 'mdast' + +const mdast: Root = { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'text', + value: 'example' + } + ] + } + ] +} +``` + +#### `unist-builder` + +It’s also possible to build trees with [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme). +It allows a more concise, hyperscript (similar to `React.createElement`) like +syntax: + +```ts +import {u} from 'unist-builder' + +const mdast = u('root', [ + u('paragraph', [ + u('text', 'example') + ]) +]) +``` + +#### `hastscript` + +When working with hast (HTML), [`hastscript`](https://github.com/syntax-tree/hastscript#readme) +can be used. + +```ts +import {h} from 'hastscript' + +console.log( + h('div#some-id.foo', [ + h('span', 'some text'), + h('input', {type: 'text', value: 'foo'}), + h('a.alpha.bravo.charlie', {download: true}, 'delta') + ]) +) +``` + +hastscript can also be used as a JSX pragma: + +```tsx +/** @jsx h @jsxFrag null */ +import {h} from 'hastscript' + +console.log( + + + + + +) +``` + +#### `xastscript` + +When working with xast (XML), [`xastscript`](https://github.com/syntax-tree/xastscript#readme) +can be used. + +```ts +import {x} from 'xastscript' + +console.log( + x('album', {id: 123}, [ + x('name', 'Exile in Guyville'), + x('artist', 'Liz Phair'), + x('releasedate', '1993-06-22') + ]) +) +``` + +xastscript can also be used as a JSX pragma: + +```tsx +/** @jsx x @jsxFrag null */ +import {x} from 'xastscript' + +console.log( + + Born in the U.S.A. + Bruce Springsteen + 1984-04-06 + +) +``` diff --git a/doc/learn/node-type-narrowing-in-typescript.md b/doc/learn/node-type-narrowing-in-typescript.md new file mode 100644 index 00000000000..360f493675e --- /dev/null +++ b/doc/learn/node-type-narrowing-in-typescript.md @@ -0,0 +1,73 @@ +--- +group: recipe +index: 3 +title: Tree traversal +description: How to do tree traversal (also known as walking or visiting a tree) +tags: + - typescript + - unist + - mdast +author: Christian Murphy +authorGithub: ChristianMurphy +published: 2020-06-09 +modified: 2020-06-11 +--- + +## How to narrow generic `Node` to specific syntax types + +To work with a specific node type or a set of node types we need to +[narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) their +type. +For example, we can take a `Node` and perform a type safe check to get a more +specific type like a `Link`. +Unified provides a utility to help with this and there are some TypeScript +language features which can also help. +Let’s first take a look at `unist-util-is`. + +[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a +`Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) +and returns `true` if the test passes, and `false` if it does not. It also is +a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) +meaning if used as the condition for an `if` statement, TypeScript knows more +about the type inside the `if`. + +For example: + +```ts +import type {Node, Literal} from 'unist' +import type {List, Blockquote, Strong, Emphasis, Heading} from 'mdast' +import {is, convert} from 'unist-util-is' + +// `Node` could come from a plugin, a utility, or be passed into a function +// here we hard code a Node for testing purposes +const node: Node = {type: 'example'} + +if (is(node, 'list')) { + // If we get here, node is List +} + +if (is(node, ['strong', 'emphasis'])) { + // If we get here, node is Strong or Emphasis + + // If we want even more specific type, we can use a discriminated union + // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions + if (node.type === 'emphasis') { + // If we get here, node is Emphasis + } +} + +if (is(node, {type: 'heading', depth: 1})) { + // If we get here, node is Heading +} + +// For advanced use cases, another predicate can be passed to `is` +if (is(node, (node: Node): node is Literal => 'value' in node)) { + // If we get here, node is one of the Literal types +} + +// Reusable predicates can also be created using any `Test` +const isBlockquote = convert
('blockquote') +if (isBlockquote(node)) { + // If we get here, node is Blockquote +} +``` diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 0db0929c66d..3296c52dc97 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -1,13 +1,17 @@ --- group: guide -title: Working with syntax trees in TypeScript -description: Guide that shows how to traverse, update, and create syntax trees in TypeScript +title: Typing syntax trees with TypeScript +description: Guide shows how to use types packages to work with syntax trees author: Christian Murphy authorGithub: ChristianMurphy tags: - typescript + - unist + - mdast + - hast + - xast published: 2020-06-09 -modified: 2020-06-09 +modified: 2020-06-11 --- ## Working with syntax trees in TypeScript @@ -16,14 +20,15 @@ This guide will introduce you to using unist and unified with TypeScript. ### Contents -* [The basic syntax tree types](#the-basic-syntax-tree-types) -* [The Languages](#the-languages) -* [How to traverse a syntax tree](#how-to-traverse-a-syntax-tree) -* [How to narrow generic `Node` to specific syntax types](#how-to-narrow-generic-node-to-specific-syntax-types) -* [How to build syntax tree](#how-to-build-syntax-tree) +* [The Basics](#the-basics) +* [UNIST](#unist) +* [MDAST (Markdown)](#mdast-markdown) +* [HAST (HTML)](#hast-html) +* [XAST (XML)](#xast-xml) * [Summary](#summary) +* [Next steps](#next-steps) -### The basic syntax tree types +### The Basics All unified syntax trees are based off [unist (**uni**versal **s**yntax **t**ree)](https://github.com/syntax-tree/unist). The core types are available in a types only package: [`@types/unist`](https://www.npmjs.com/package/@types/unist). @@ -31,6 +36,16 @@ The main type is `Node`. Everything else extends it. `Literal` and `Parent` are more practical types which also extend `Node`. +The types provided by unist are abstract interfaces. +In many cases, you will instead use more specific interfaces depending on what +language you’re working with. +Each language supported by unified, like markdown, HTML, and XML, has its own +syntax tree standard which extends `unist`. + +Let’s take a look at these. + +### UNIST + #### `Node` `Node` is the syntactic unit of syntax trees. @@ -160,16 +175,7 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- */ ``` -### The Languages - -The types provided by unist are abstract interfaces. -In many cases, you will instead use more specific interfaces depending on what -language you’re working with. -Each language supported by unified, like markdown, HTML, and XML, has its own -syntax tree standard which extends `unist`. -Let’s take a look at a few of them. - -#### MDAST (Markdown) +### MDAST (Markdown) [mdast (**m**arkdown **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/mdast#readme) extends unist with types specific for markdown such as `Heading`, `Code`, @@ -199,7 +205,7 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha */ ``` -#### HAST (HTML) +### HAST (HTML) [hast (**h**ypertext **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/hast#readme) extends unist with types specific for HTML such as `Element`, `Comment`, @@ -229,7 +235,7 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha */ ``` -#### XAST (XML) +### XAST (XML) [xast (e**x**tensible **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/xast#readme) extends unist with types specific for HTML such as `Element`, `CData`, @@ -259,312 +265,13 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha */ ``` -### How to traverse a syntax tree - -:notebook: please read the [introduction to tree traversal in JavaScript](./tree-traversal/) -before reading this section. - -A frequent task when working with unified is to traverse trees to find certain -nodes and then doing something with them (often validating or transforming -them). -There are several type-safe utilities provided by unified to help with this. - -#### `unist-util-visit` - -[`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) -takes a syntax tree, a `Test`, and a callback. -The callback is called for each node in the tree that passes `Test`. - -For example if we want to increasing the heading level of all headings in a -markdown document: - -```ts -import remark from 'remark' -import type {Node} from 'unist' -import type {Heading} from 'mdast' -import {visit} from 'unist-util-visit' - -const markdownFile = await remark() - .use(() => (mdast: Node) => { - visit( - mdast, - // Check that the Node is a heading: - 'heading', - (node: Heading) => { - node.depth += 1 - } - ) - }) - .process('## Hello, *World*!') - -console.log(markdownFile.toString()) -``` - -Or if we want to make all ordered lists in a markdown document unordered: - -```ts -import remark from 'remark' -import type {Node} from 'unist' -import type {List} from 'mdast' -import {visit} from 'unist-util-visit' - -const markdownFile = await remark() - .use(() => (mdast: Node) => { - visit( - mdast, - // Check that the Node is a list and that it is ordered: - {type: 'list', ordered: true}, - (node: List) => { - node.ordered = false - } - ) - }) - .process('1. list item') - -console.log(markdownFile.toString()) -``` - -#### `unist-util-visit-parents` - -Sometimes it’s needed to know the ancestors of a node (all its parents). -[`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) -is like `unist-util-visit` but includes a list of all parent nodes. - -For example if we want to check if all markdown `ListItem` are inside a `List` -we could: - -```ts -import remark from 'remark' -import type {Node, Parent} from 'unist' -import type {ListItem} from 'mdast' -import {visitParents} from 'unist-util-visit-parents' - -remark() - .use(() => (mdast: Node) => { - visitParents(mdast, 'listItem', (listItem: ListItem, parents: Parent[]) => { - if (!parents.some((parent) => parent.type === 'list')) { - console.warn('listItem is outside a list') - } - }) - }) - .process('1. list item') -``` - -#### `unist-util-select` - -Sometimes CSS selectors are easier to read than several (nested) if/else -statements. -[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) lets -you do just that. -For example if we want to find all `Paragraph`s that are somewhere in a -`Blockquote`, we could: - -```ts -import remark from 'remark' -import type {Node} from 'unist' -import {selectAll} from 'unist-util-select' - -remark() - .use(() => (mdast: Node) => { - const matches = selectAll('blockquote paragraph', mdast) - console.log(matches) - }) - .process('1. list item') -``` - -### How to narrow generic `Node` to specific syntax types - -To work with a specific node type or a set of node types we need to -[narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) their -type. -For example, we can take a `Node` and perform a type safe check to get a more -specific type like a `Link`. -Unified provides a utility to help with this and there are some TypeScript -language features which can also help. -Let’s first take a look at `unist-util-is`. - -[`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a -`Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) -and returns `true` if the test passes, and `false` if it does not. It also is -a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) -meaning if used as the condition for an `if` statement, TypeScript knows more -about the type inside the `if`. - -For example: - -```ts -import type {Node, Literal} from 'unist' -import type {List, Blockquote, Strong, Emphasis, Heading} from 'mdast' -import {is, convert} from 'unist-util-is' - -// `Node` could come from a plugin, a utility, or be passed into a function -// here we hard code a Node for testing purposes -const node: Node = {type: 'example'} - -if (is(node, 'list')) { - // If we get here, node is List -} - -if (is(node, ['strong', 'emphasis'])) { - // If we get here, node is Strong or Emphasis - - // If we want even more specific type, we can use a discriminated union - // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions - if (node.type === 'emphasis') { - // If we get here, node is Emphasis - } -} - -if (is(node, {type: 'heading', depth: 1})) { - // If we get here, node is Heading -} - -// For advanced use cases, another predicate can be passed to `is` -if (is(node, (node: Node): node is Literal => 'value' in node)) { - // If we get here, node is one of the Literal types -} - -// Reusable predicates can also be created using any `Test` -const isBlockquote = convert
('blockquote') -if (isBlockquote(node)) { - // If we get here, node is Blockquote -} -``` - -### How to build syntax tree - -It’s often useful to build new (fragments of) syntax trees when adding or -replacing content. -It’s possible to create trees with plain object and array literals (JSON) or -programmatically with a small utility. -Finally it’s even possible to use JSX to build trees. - -#### JSON - -The most basic way to create a tree is with plain object and arrays, such as: - -```ts -const mdast = { - type: 'root', - children: [ - { - type: 'paragraph', - children: [ - { - type: 'text', - value: 'example' - } - ] - } - ] -} -``` - -for some extra type safety this can be checked with the types for the given -syntax tree language, in this case MDAST: - -```ts -import type {Root} from 'mdast' - -const mdast: Root = { - type: 'root', - children: [ - { - type: 'paragraph', - children: [ - { - type: 'text', - value: 'example' - } - ] - } - ] -} -``` - -#### `unist-builder` - -It’s also possible to build trees with [`unist-builder`](https://github.com/syntax-tree/unist-builder#readme). -It allows a more concise, hyperscript (similar to `React.createElement`) like -syntax: - -```ts -import {u} from 'unist-builder' - -const mdast = u('root', [ - u('paragraph', [ - u('text', 'example') - ]) -]) -``` - -#### `hastscript` - -When working with hast (HTML), [`hastscript`](https://github.com/syntax-tree/hastscript#readme) -can be used. - -```ts -import {h} from 'hastscript' - -console.log( - h('div#some-id.foo', [ - h('span', 'some text'), - h('input', {type: 'text', value: 'foo'}), - h('a.alpha.bravo.charlie', {download: true}, 'delta') - ]) -) -``` - -hastscript can also be used as a JSX pragma: - -```tsx -/** @jsx h @jsxFrag null */ -import {h} from 'hastscript' - -console.log( -
- - - -
-) -``` - -#### `xastscript` - -When working with xast (XML), [`xastscript`](https://github.com/syntax-tree/xastscript#readme) -can be used. - -```ts -import {x} from 'xastscript' - -console.log( - x('album', {id: 123}, [ - x('name', 'Exile in Guyville'), - x('artist', 'Liz Phair'), - x('releasedate', '1993-06-22') - ]) -) -``` - -xastscript can also be used as a JSX pragma: +### Summary -```tsx -/** @jsx x @jsxFrag null */ -import {x} from 'xastscript' +* Unified provides types for each language’s syntax tree +* These types can be import into TypeScript projects and into JSDoc projects -console.log( - - Born in the U.S.A. - Bruce Springsteen - 1984-04-06 - -) -``` - -### Summary +### Next steps -* Unified provides types for each language’s syntax tree and utilities to - work with these types -* Types are available for most plugins and utilities (and if types haven’t - been added a pull request is welcome!) +* [Learn to traverse syntax trees with TypeScript](/learn/recipe/tree-traversal-typescript) +* [Learn to narrow `Node` to a more specific type with TypeScript](/learn/recipe/node-type-narrowing-in-typescript) +* [Learn to build content with syntax trees in TypeScript](/learn/recipe/build-a-syntax-tree-typescript) diff --git a/doc/learn/tree-traversal-typescript.md b/doc/learn/tree-traversal-typescript.md new file mode 100644 index 00000000000..83cd4820cf9 --- /dev/null +++ b/doc/learn/tree-traversal-typescript.md @@ -0,0 +1,129 @@ +--- +group: recipe +index: 3 +title: Tree traversal with TypeScript +description: How to do tree traversal (also known as walking or visiting a tree) +tags: + - unist + - tree + - traverse + - walk + - visit +author: Christian Murphy +authorGithub: ChristianMurphy +published: 2020-06-09 +modified: 2020-06-11 +--- + +## How to traverse a syntax tree + +:notebook: please read the [introduction to tree traversal in JavaScript](./tree-traversal/) +before reading this section. + +A frequent task when working with unified is to traverse trees to find certain +nodes and then doing something with them (often validating or transforming +them). +There are several type-safe utilities provided by unified to help with this. + +### `unist-util-visit` + +[`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit#readme) +takes a syntax tree, a `Test`, and a callback. +The callback is called for each node in the tree that passes `Test`. + +For example if we want to increasing the heading level of all headings in a +markdown document: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import type {Heading} from 'mdast' +import {visit} from 'unist-util-visit' + +const markdownFile = await remark() + .use(() => (mdast: Node) => { + visit( + mdast, + // Check that the Node is a heading: + 'heading', + (node: Heading) => { + node.depth += 1 + } + ) + }) + .process('## Hello, *World*!') + +console.log(markdownFile.toString()) +``` + +Or if we want to make all ordered lists in a markdown document unordered: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import type {List} from 'mdast' +import {visit} from 'unist-util-visit' + +const markdownFile = await remark() + .use(() => (mdast: Node) => { + visit( + mdast, + // Check that the Node is a list and that it is ordered: + {type: 'list', ordered: true}, + (node: List) => { + node.ordered = false + } + ) + }) + .process('1. list item') + +console.log(markdownFile.toString()) +``` + +### `unist-util-visit-parents` + +Sometimes it’s needed to know the ancestors of a node (all its parents). +[`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) +is like `unist-util-visit` but includes a list of all parent nodes. + +For example if we want to check if all markdown `ListItem` are inside a `List` +we could: + +```ts +import remark from 'remark' +import type {Node, Parent} from 'unist' +import type {ListItem} from 'mdast' +import {visitParents} from 'unist-util-visit-parents' + +remark() + .use(() => (mdast: Node) => { + visitParents(mdast, 'listItem', (listItem: ListItem, parents: Parent[]) => { + if (!parents.some((parent) => parent.type === 'list')) { + console.warn('listItem is outside a list') + } + }) + }) + .process('1. list item') +``` + +### `unist-util-select` + +Sometimes CSS selectors are easier to read than several (nested) if/else +statements. +[`unist-util-select`](https://github.com/syntax-tree/unist-util-select) lets +you do that. +For example if we want to find all `Paragraph`s that are somewhere in a +`Blockquote`, we could: + +```ts +import remark from 'remark' +import type {Node} from 'unist' +import {selectAll} from 'unist-util-select' + +remark() + .use(() => (mdast: Node) => { + const matches = selectAll('blockquote paragraph', mdast) + console.log(matches) + }) + .process('1. list item') +``` From 9f48cfe6f92cee501525dc71380c3745cedcd439 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Sun, 11 Jul 2021 15:01:41 -0700 Subject: [PATCH 09/14] docs: improve commentary on how type narrowing works --- .../node-type-narrowing-in-typescript.md | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/doc/learn/node-type-narrowing-in-typescript.md b/doc/learn/node-type-narrowing-in-typescript.md index 360f493675e..79639f8ddce 100644 --- a/doc/learn/node-type-narrowing-in-typescript.md +++ b/doc/learn/node-type-narrowing-in-typescript.md @@ -25,11 +25,10 @@ language features which can also help. Let’s first take a look at `unist-util-is`. [`unist-util-is`](https://github.com/syntax-tree/unist-util-is#readme) takes a -`Node` and a [`Test`](https://github.com/syntax-tree/unist-util-is#isnode-test-index-parent-context) -and returns `true` if the test passes, and `false` if it does not. It also is -a [TypeScript predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) -meaning if used as the condition for an `if` statement, TypeScript knows more -about the type inside the `if`. +`Node` and a `Test` and returns whether the test passes. +It can be used as a [TypeScript type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) +which when used as a condition (such as in an if-statement) tells TypeScript +to narrow a node. For example: @@ -43,7 +42,14 @@ import {is, convert} from 'unist-util-is' const node: Node = {type: 'example'} if (is(node, 'list')) { - // If we get here, node is List + // If we're here, node is List. + // + // 'list' is compared to node.type to make sure they match + // true means a match, false means no match + // + // tells TypeScript to ensure 'list' matches List.type + // and that if 'list' matches both node.type and List.type + // we know that node is List within this if condition. } if (is(node, ['strong', 'emphasis'])) { @@ -58,11 +64,22 @@ if (is(node, ['strong', 'emphasis'])) { if (is(node, {type: 'heading', depth: 1})) { // If we get here, node is Heading + // + // Typescript checks that the properties used in the Test + // are valid attributes of + // + // It does not narrow node.depth only be 1, + // which can be done with } // For advanced use cases, another predicate can be passed to `is` if (is(node, (node: Node): node is Literal => 'value' in node)) { // If we get here, node is one of the Literal types + // + // Here any comparison function can be used, as long as it is a predicate + // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates + // and as long as the predicate and generic match. + // For example here, and `is Literal` match. } // Reusable predicates can also be created using any `Test` @@ -70,4 +87,4 @@ const isBlockquote = convert
('blockquote') if (isBlockquote(node)) { // If we get here, node is Blockquote } -``` +``` \ No newline at end of file From 156768a2121423d06512f4e319a34d0fedcb6672 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Tue, 13 Jul 2021 12:54:38 -0700 Subject: [PATCH 10/14] docs: apply suggestions from code review Co-authored-by: Titus --- doc/learn/syntax-trees-with-typescript.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 3296c52dc97..6ca76ab88fb 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -28,7 +28,7 @@ This guide will introduce you to using unist and unified with TypeScript. * [Summary](#summary) * [Next steps](#next-steps) -### The Basics +### The basics All unified syntax trees are based off [unist (**uni**versal **s**yntax **t**ree)](https://github.com/syntax-tree/unist). The core types are available in a types only package: [`@types/unist`](https://www.npmjs.com/package/@types/unist). @@ -44,21 +44,21 @@ syntax tree standard which extends `unist`. Let’s take a look at these. -### UNIST +### unist #### `Node` `Node` is the syntactic unit of syntax trees. Each node extends `Node` (sometimes indirectly through `Literal` or `Parent`) -and set `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) -The type field tells us what kind of syntax the node is. +and sets `type` to a [string literal](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +The type field tells us what kind of content the node is. This field uniquely identifies a kind of content (in TypeScript parlance a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)). -For example in Markdown (mdast) `Node` will be extended to make different kinds -of content such as a `Heading` or `Link`, which respectively use a `type` field +For example in markdown (mdast) `Node` will be extended to make different things +such as a `Heading` or `Link`, which respectively use a `type` field of `'heading'` and `'link'`. -A node also can optionally include a `Data` interface at the `data` field. +A node can optionally include a `Data` interface at the `data` field. This is an object (dictionary) that stores extra metadata which is not standard to the node but defined by the ecosystem (utilities and plugins). @@ -175,7 +175,7 @@ or into a [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro- */ ``` -### MDAST (Markdown) +### mdast (markdown) [mdast (**m**arkdown **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/mdast#readme) extends unist with types specific for markdown such as `Heading`, `Code`, @@ -205,7 +205,7 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha */ ``` -### HAST (HTML) +### hast (HTML) [hast (**h**ypertext **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/hast#readme) extends unist with types specific for HTML such as `Element`, `Comment`, @@ -235,7 +235,7 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha */ ``` -### XAST (XML) +### xast (XML) [xast (e**x**tensible **a**bstract **s**yntax **t**ree)](https://github.com/syntax-tree/xast#readme) extends unist with types specific for HTML such as `Element`, `CData`, From 379cb7c82e3741828b32c82222a8781a0340f7d0 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Thu, 15 Jul 2021 16:23:19 -0700 Subject: [PATCH 11/14] style: format documents, update metadata, fix grammar --- doc/learn/build-a-syntax-tree-typescript.md | 3 ++- .../node-type-narrowing-in-typescript.md | 2 +- doc/learn/syntax-trees-with-typescript.md | 22 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/learn/build-a-syntax-tree-typescript.md b/doc/learn/build-a-syntax-tree-typescript.md index 55125791d31..c0eaddb2e8b 100644 --- a/doc/learn/build-a-syntax-tree-typescript.md +++ b/doc/learn/build-a-syntax-tree-typescript.md @@ -10,10 +10,11 @@ tags: - builder - hyperscript - jsx + - typescript author: Christian Murphy authorGithub: ChristianMurphy published: 2020-06-09 -modified: 2020-06-11 +modified: 2020-06-15 --- ## How to build syntax tree diff --git a/doc/learn/node-type-narrowing-in-typescript.md b/doc/learn/node-type-narrowing-in-typescript.md index 79639f8ddce..7bcd0fa00b7 100644 --- a/doc/learn/node-type-narrowing-in-typescript.md +++ b/doc/learn/node-type-narrowing-in-typescript.md @@ -87,4 +87,4 @@ const isBlockquote = convert
('blockquote') if (isBlockquote(node)) { // If we get here, node is Blockquote } -``` \ No newline at end of file +``` diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index 6ca76ab88fb..de63dbbf1a5 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -1,7 +1,7 @@ --- group: guide title: Typing syntax trees with TypeScript -description: Guide shows how to use types packages to work with syntax trees +description: Guide that shows how to use types packages to work with syntax trees author: Christian Murphy authorGithub: ChristianMurphy tags: @@ -11,7 +11,7 @@ tags: - hast - xast published: 2020-06-09 -modified: 2020-06-11 +modified: 2020-06-15 --- ## Working with syntax trees in TypeScript @@ -20,11 +20,11 @@ This guide will introduce you to using unist and unified with TypeScript. ### Contents -* [The Basics](#the-basics) -* [UNIST](#unist) -* [MDAST (Markdown)](#mdast-markdown) -* [HAST (HTML)](#hast-html) -* [XAST (XML)](#xast-xml) +* [The basics](#the-basics) +* [unist](#unist) +* [mdast (markdown)](#mdast-markdown) +* [hast (HTML)](#hast-html) +* [xast (XML)](#xast-xml) * [Summary](#summary) * [Next steps](#next-steps) @@ -123,7 +123,7 @@ export interface Position { #### `Literal` `Literal` extends `Node` and adds a `value` property. -For example a markdown `code` node extends `Literal` and sets `value` to be a `string`. +For example a markdown `Code` node extends `Literal` and sets `value` to be a `string`. ```ts /** @@ -272,6 +272,6 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha ### Next steps -* [Learn to traverse syntax trees with TypeScript](/learn/recipe/tree-traversal-typescript) -* [Learn to narrow `Node` to a more specific type with TypeScript](/learn/recipe/node-type-narrowing-in-typescript) -* [Learn to build content with syntax trees in TypeScript](/learn/recipe/build-a-syntax-tree-typescript) +* [Learn to traverse syntax trees with TypeScript](/learn/recipe/tree-traversal-typescript/) +* [Learn to narrow `Node` to a more specific type with TypeScript](/learn/recipe/node-type-narrowing-in-typescript/) +* [Learn to build content with syntax trees in TypeScript](/learn/recipe/build-a-syntax-tree-typescript/) From 4185d7e325dc2a117972ab48e682c45a13ea95b6 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Thu, 15 Jul 2021 16:25:39 -0700 Subject: [PATCH 12/14] docs: add svg example Co-authored-by: Titus --- doc/learn/build-a-syntax-tree-typescript.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/learn/build-a-syntax-tree-typescript.md b/doc/learn/build-a-syntax-tree-typescript.md index c0eaddb2e8b..bcba3748c21 100644 --- a/doc/learn/build-a-syntax-tree-typescript.md +++ b/doc/learn/build-a-syntax-tree-typescript.md @@ -34,6 +34,7 @@ language, in this case MDAST: ```ts import type {Root} from 'mdast' +// Note the `: Root` is a TypeScript annotation. Remove it (and the import) for plain JavaScript. const mdast: Root = { type: 'root', children: [ @@ -72,7 +73,7 @@ When working with hast (HTML), [`hastscript`](https://github.com/syntax-tree/has can be used. ```ts -import {h} from 'hastscript' +import {h, s} from 'hastscript' console.log( h('div#some-id.foo', [ @@ -81,6 +82,14 @@ console.log( h('a.alpha.bravo.charlie', {download: true}, 'delta') ]) ) + +// SVG: +console.log( + s('svg', {xmlns: 'http://www.w3.org/2000/svg', viewbox: '0 0 500 500'}, [ + s('title', 'SVG `` element'), + s('circle', {cx: 120, cy: 120, r: 100}) + ]) +) ``` hastscript can also be used as a JSX pragma: From 89ade484b986a8495af6b104152d71098bfb2ce1 Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Thu, 15 Jul 2021 16:30:34 -0700 Subject: [PATCH 13/14] rename build syntax tree recipe --- ...build-a-syntax-tree-typescript.md => build-a-syntax-tree.md} | 0 doc/learn/syntax-trees-with-typescript.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/learn/{build-a-syntax-tree-typescript.md => build-a-syntax-tree.md} (100%) diff --git a/doc/learn/build-a-syntax-tree-typescript.md b/doc/learn/build-a-syntax-tree.md similarity index 100% rename from doc/learn/build-a-syntax-tree-typescript.md rename to doc/learn/build-a-syntax-tree.md diff --git a/doc/learn/syntax-trees-with-typescript.md b/doc/learn/syntax-trees-with-typescript.md index de63dbbf1a5..6ac18763bbc 100644 --- a/doc/learn/syntax-trees-with-typescript.md +++ b/doc/learn/syntax-trees-with-typescript.md @@ -274,4 +274,4 @@ To import the types in [JSDoc TypeScript](https://www.typescriptlang.org/docs/ha * [Learn to traverse syntax trees with TypeScript](/learn/recipe/tree-traversal-typescript/) * [Learn to narrow `Node` to a more specific type with TypeScript](/learn/recipe/node-type-narrowing-in-typescript/) -* [Learn to build content with syntax trees in TypeScript](/learn/recipe/build-a-syntax-tree-typescript/) +* [Learn to build content with syntax trees in TypeScript](/learn/recipe/build-a-syntax-tree/) From a00e5e33763e63bffdd29c5f5da03ea7f416b41d Mon Sep 17 00:00:00 2001 From: Christian Murphy Date: Thu, 15 Jul 2021 16:34:17 -0700 Subject: [PATCH 14/14] add indices to recipes --- doc/learn/build-a-syntax-tree.md | 2 +- doc/learn/node-type-narrowing-in-typescript.md | 4 ++-- doc/learn/tree-traversal-typescript.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/learn/build-a-syntax-tree.md b/doc/learn/build-a-syntax-tree.md index bcba3748c21..48be9fc83e2 100644 --- a/doc/learn/build-a-syntax-tree.md +++ b/doc/learn/build-a-syntax-tree.md @@ -1,6 +1,6 @@ --- group: recipe -index: 3 +index: 8 title: Building a content syntax tree description: How to build content with syntax trees tags: diff --git a/doc/learn/node-type-narrowing-in-typescript.md b/doc/learn/node-type-narrowing-in-typescript.md index 7bcd0fa00b7..987f6e7f53a 100644 --- a/doc/learn/node-type-narrowing-in-typescript.md +++ b/doc/learn/node-type-narrowing-in-typescript.md @@ -1,6 +1,6 @@ --- group: recipe -index: 3 +index: 10 title: Tree traversal description: How to do tree traversal (also known as walking or visiting a tree) tags: @@ -10,7 +10,7 @@ tags: author: Christian Murphy authorGithub: ChristianMurphy published: 2020-06-09 -modified: 2020-06-11 +modified: 2020-06-15 --- ## How to narrow generic `Node` to specific syntax types diff --git a/doc/learn/tree-traversal-typescript.md b/doc/learn/tree-traversal-typescript.md index 83cd4820cf9..bce3aab761e 100644 --- a/doc/learn/tree-traversal-typescript.md +++ b/doc/learn/tree-traversal-typescript.md @@ -1,6 +1,6 @@ --- group: recipe -index: 3 +index: 9 title: Tree traversal with TypeScript description: How to do tree traversal (also known as walking or visiting a tree) tags: