Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TypeScript guide and recipes #37

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions doc/learn/build-a-syntax-tree-typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
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
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
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 = {
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
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'
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved

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')
])
)
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
```

hastscript can also be used as a JSX pragma:

```tsx
/** @jsx h @jsxFrag null */
import {h} from 'hastscript'

console.log(
<form method="POST">
<input type="text" name="foo" />
<input type="text" name="bar" />
<input type="submit" name="send" />
</form>
)
```

#### `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(
<album id={123}>
<name>Born in the U.S.A.</name>
<artist>Bruce Springsteen</artist>
<releasedate>1984-04-06</releasedate>
</album>
)
```
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
90 changes: 90 additions & 0 deletions doc/learn/node-type-narrowing-in-typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
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
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
---

## 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` 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:

```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<List>(node, '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
//
// <List> 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<Strong | Emphasis>(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<Heading>(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 <Heading>
//
// It does not narrow node.depth only be 1,
// which can be done with <Heading & {depth: 1}>
}

// For advanced use cases, another predicate can be passed to `is`
if (is<Literal>(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, <Literal> and `is Literal` match.
}

// Reusable predicates can also be created using any `Test`
const isBlockquote = convert<Blockquote>('blockquote')
if (isBlockquote(node)) {
// If we get here, node is Blockquote
}
```
ChristianMurphy marked this conversation as resolved.
Show resolved Hide resolved
Loading