Skip to content

Commit

Permalink
Refactor code-style
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 6, 2023
1 parent c191c56 commit 57992a3
Show file tree
Hide file tree
Showing 5 changed files with 462 additions and 451 deletions.
333 changes: 180 additions & 153 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,201 +1,228 @@
/* eslint-disable @typescript-eslint/no-empty-function */

import {expectError, expectType} from 'tsd'
import type {Node, Parent, Literal} from 'unist'
import {is} from 'unist-util-is'
import {visit, SKIP, EXIT, CONTINUE} from './index.js'
import {expectAssignable, expectNotType, expectType} from 'tsd'
import type {
Blockquote,
Content,
Definition,
Delete,
Emphasis,
Footnote,
FootnoteDefinition,
Heading,
Link,
LinkReference,
ListItem,
PhrasingContent,
Root,
Strong,
TableCell,
TableRow
} from 'mdast'
import type {Node, Parent} from 'unist'
import {CONTINUE, EXIT, SKIP, visit} from './index.js'

// To do: use `mdast` when released.
type Nodes = Root | Content

// To do: use `mdast` when released.
type Parents = Extract<Nodes, Parent>

/* Setup */
const sampleTree: Root = {
const implicitTree = {
type: 'root',
children: [{type: 'heading', depth: 1, children: []}]
}

const complexTree: Root = {
const sampleTree: Root = {
type: 'root',
children: [
{
type: 'blockquote',
children: [{type: 'paragraph', children: [{type: 'text', value: 'a'}]}]
},
{
type: 'paragraph',
children: [
{
type: 'emphasis',
children: [{type: 'emphasis', children: [{type: 'text', value: 'b'}]}]
},
{type: 'text', value: 'c'}
]
}
]
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Element extends Parent {
type: 'element'
tagName: string
properties: Record<string, unknown>
content: Node
children: Array<Node>
}

type Content = Flow | Phrasing

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Root extends Parent {
type: 'root'
children: Array<Flow>
}

type Flow = Blockquote | Heading | Paragraph

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Blockquote extends Parent {
type: 'blockquote'
children: Array<Flow>
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Heading extends Parent {
type: 'heading'
depth: number
children: Array<Phrasing>
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Paragraph extends Parent {
type: 'paragraph'
children: Array<Phrasing>
}

type Phrasing = Text | Emphasis

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Emphasis extends Parent {
type: 'emphasis'
children: Array<Phrasing>
children: [{type: 'heading', depth: 1, children: []}]
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Text extends Literal {
type: 'text'
value: string
}
// ## Missing parameters
// @ts-expect-error: check that `node` is passed.
visit()
// @ts-expect-error: check that `visitor` is passed.
visit(sampleTree)

// ## No test
visit(sampleTree, function (node, index, parent) {
expectType<Nodes>(node)
expectType<number | null>(index)
expectType<Parents | null>(parent)
})

const isNode = (node: unknown): node is Node =>
typeof node === 'object' && node !== null && 'type' in node
const headingTest = (node: unknown): node is Heading =>
isNode(node) && node.type === 'heading'
const elementTest = (node: unknown): node is Element =>
isNode(node) && node.type === 'element'
visit(implicitTree, function (node, index, parent) {
// Objects are too loose.
expectAssignable<Node>(node)
expectNotType<Node>(node)
expectType<number | null>(index)
expectType<never>(parent)
})

/* Missing params. */
expectError(visit())
expectError(visit(sampleTree))
// ## String test

/* Visit without test. */
visit(sampleTree, (node, _, parent) => {
expectType<Root | Content>(node)
expectType<Extract<Root | Content, Parent> | null>(parent)
// Knows it’s a heading and its parents.
visit(sampleTree, 'heading', function (node, index, parent) {
expectType<Heading>(node)
expectType<number | null>(index)
expectType<Blockquote | FootnoteDefinition | ListItem | Root | null>(parent)
})

/* Visit with type test. */
visit(sampleTree, 'heading', (node, _, parent) => {
expectType<Heading>(node)
expectType<Root | Blockquote | null>(parent)
// Not in tree.
visit(sampleTree, 'element', function (node, index, parent) {
expectType<never>(node)
expectType<never>(index)
expectType<never>(parent)
})
visit(sampleTree, 'element', (node, index, parent) => {
// Not in tree.

// Implicit nodes are too loose.
visit(implicitTree, 'heading', function (node, index, parent) {
expectType<never>(node)
expectType<never>(index)
expectType<never>(parent)
})
expectError(visit(sampleTree, 'heading', (_: Element) => {}))

/* Visit with object test. */
visit(sampleTree, {depth: 1}, (node) => {
expectType<Heading>(node)
visit(sampleTree, 'tableCell', function (node, index, parent) {
expectType<TableCell>(node)
expectType<number | null>(index)
expectType<Root | TableRow | null>(parent)
})
visit(sampleTree, {random: 'property'}, (node) => {
expectType<never>(node)

// ## Props test

// Knows that headings have depth, but TS doesn’t infer the depth normally.
visit(sampleTree, {depth: 1}, function (node) {
expectType<Heading>(node)
expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth)
})
visit(sampleTree, {type: 'heading', depth: '2'}, (node) => {
// Not in tree.
expectType<never>(node)

// This goes fine.
visit(sampleTree, {type: 'heading'} as const, function (node) {
expectType<Heading>(node)
expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth)
})
visit(sampleTree, {tagName: 'section'}, (node) => {
// Not in tree.

// For some reason the const goes wrong.
visit(sampleTree, {depth: 1} as const, function (node) {
// Note: something going wrong here, to do: investigate.
expectType<never>(node)
})
visit(sampleTree, {type: 'element', tagName: 'section'}, (node) => {
// Not in tree.

// For some reason the const goes wrong.
visit(sampleTree, {type: 'heading', depth: 1} as const, function (node) {
// Note: something going wrong here, to do: investigate.
expectType<never>(node)
})

/* Visit with function test. */
visit(sampleTree, headingTest, (node) => {
// Function test (implicit assertion).
visit(sampleTree, isHeadingLoose, function (node) {
expectType<Nodes>(node)
})
// Function test (explicit assertion).
visit(sampleTree, isHeading, function (node) {
expectType<Heading>(node)
expectType<1 | 2 | 3 | 4 | 5 | 6>(node.depth)
})
expectError(visit(sampleTree, headingTest, (_: Element) => {}))
visit(sampleTree, elementTest, (node) => {
// Not in tree.
// Function test (explicit assertion).
visit(sampleTree, isHeading2, function (node) {
// To do: improving `InclusiveDescendant` should use `Heading & {depth: 2}`.
expectType<never>(node)
})

/* Visit with array of tests. */
visit(sampleTree, ['heading', {depth: 1}, headingTest], (node) => {
// ## Combined tests
visit(sampleTree, ['heading', {depth: 1}, isHeading], function (node) {
// Unfortunately TS casts things in arrays too vague.
expectType<Root | Content>(node)
})

/* Visit returns action. */
visit(sampleTree, () => CONTINUE)
visit(sampleTree, () => EXIT)
visit(sampleTree, () => SKIP)
expectError(visit(sampleTree, () => 'random'))
// To do: update to `unist-util-is` should make this work?
// visit(
// sampleTree,
// ['heading', {depth: 1}, isHeading] as const,
// function (node) {
// // Unfortunately TS casts things in arrays too vague.
// expectType<Root | Content>(node)
// }
// )

// ## Return type: incorrect.
// @ts-expect-error: not an action.
visit(sampleTree, function () {
return 'random'
})
// @ts-expect-error: not a tuple: missing action.
visit(sampleTree, function () {
return [1]
})
// @ts-expect-error: not a tuple: incorrect action.
visit(sampleTree, function () {
return ['random', 1]
})

/* Visit returns index. */
visit(sampleTree, () => 0)
visit(sampleTree, () => 1)
// ## Return type: action.
visit(sampleTree, function () {
return CONTINUE
})
visit(sampleTree, function () {
return EXIT
})
visit(sampleTree, function () {
return SKIP
})

/* Visit returns tuple. */
visit(sampleTree, () => [CONTINUE, 1])
visit(sampleTree, () => [EXIT, 1])
visit(sampleTree, () => [SKIP, 1])
visit(sampleTree, () => [SKIP])
expectError(visit(sampleTree, () => [1]))
expectError(visit(sampleTree, () => ['random', 1]))
// ## Return type: index.
visit(sampleTree, function () {
return 0
})
visit(sampleTree, function () {
return 1
})

/* Should infer children from the given tree. */
visit(complexTree, (node, _, parent) => {
expectType<Root | Content>(node)
expectType<Extract<Root | Content, Parent> | null>(parent)
// ## Return type: tuple.
visit(sampleTree, function () {
return [CONTINUE, 1]
})
visit(sampleTree, function () {
return [EXIT, 1]
})
visit(sampleTree, function () {
return [SKIP, 1]
})
visit(sampleTree, function () {
return [SKIP]
})

const blockquote = complexTree.children[0]
if (is<Blockquote>(blockquote, 'blockquote')) {
visit(blockquote, (node, _, parent) => {
expectType<Content>(node)
expectType<Extract<Content, Parent> | null>(parent)
// ## Infer on tree
visit(sampleTree, 'tableCell', function (node) {
visit(node, function (node, _, parent) {
expectType<TableCell | PhrasingContent>(node)
expectType<
| Delete
| Emphasis
| Footnote
| Link
| LinkReference
| Strong
| TableCell
| null
>(parent)
})
}
})

const paragraph = complexTree.children[1]
if (is<Paragraph>(paragraph, 'paragraph')) {
visit(paragraph, (node, _, parent) => {
expectType<Paragraph | Phrasing>(node)
expectType<Paragraph | Emphasis | null>(parent)
visit(sampleTree, 'definition', function (node) {
visit(node, function (node, _, parent) {
expectType<Definition>(node)
expectType<never>(parent)
})
})

const child = paragraph.children[1]
function isHeading(node: Node): node is Heading {
return node ? node.type === 'heading' : false
}

function isHeading2(node: Node): node is Heading & {depth: 2} {
return isHeading(node) && node.depth === 2
}

if (is<Emphasis>(child, 'emphasis')) {
visit(child, 'blockquote', (node, index, parent) => {
// `blockquote` does not exist in phrasing.
expectType<never>(node)
expectType<never>(index)
expectType<never>(parent)
})
}
function isHeadingLoose(node: Node) {
return node ? node.type === 'heading' : false
}
Loading

0 comments on commit 57992a3

Please sign in to comment.