Skip to content

Commit aaa3c62

Browse files
committed
Refactor to move implementation to lib/
1 parent ab9e2d6 commit aaa3c62

File tree

3 files changed

+175
-165
lines changed

3 files changed

+175
-165
lines changed

index.js

Lines changed: 6 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,9 @@
11
/**
2-
* @typedef {import('unist').Parent} Parent
3-
* @typedef {import('mdast').Root|import('mdast').Content} Node
4-
* @typedef {import('mdast').Heading} Heading
5-
*
6-
* @typedef {(value: string, node: Heading) => boolean} TestFunction
7-
* Function called for each heading with its content and `node` itself check
8-
* if it’s the one to look for.
9-
*
10-
* @typedef {string|RegExp|TestFunction} Test
11-
*
12-
* @typedef Options
13-
* Configuration (optional).
14-
* @property {Test} test
15-
* Heading to look for.
16-
* When `string`, wrapped in `new RegExp('^(' + value + ')$', 'i')`;
17-
* when `RegExp`, wrapped in `function (value) {expression.test(value)}`
18-
* @property {boolean} [ignoreFinalDefinitions=false]
19-
* Ignore final definitions otherwise in the section.
20-
*
21-
* @typedef ZoneInfo
22-
* Extra info.
23-
* @property {Parent|null} parent
24-
* Parent of the range.
25-
* @property {number} start
26-
* index of `start` in `parent`
27-
* @property {number} end
28-
* index of `end` in `parent`
29-
*
30-
* @callback Handler
31-
* Callback called when a range is found.
32-
* @param {Heading|undefined} start
33-
* Start of range.
34-
* @param {Array<Node>} between
35-
* Nodes between `start` and `end`.
36-
* @param {Node|undefined} end
37-
* End of range, if any.
38-
* @param {ZoneInfo} scope
39-
* Extra info.
2+
* @typedef {import('./lib/index.js').Handler} Handler
3+
* @typedef {import('./lib/index.js').Options} Options
4+
* @typedef {import('./lib/index.js').TestFunction} TestFunction
5+
* @typedef {import('./lib/index.js').Test} Test
6+
* @typedef {import('./lib/index.js').ZoneInfo} ZoneInfo
407
*/
418

42-
import {toString} from 'mdast-util-to-string'
43-
44-
/**
45-
* Search `tree` and transform a section without affecting other parts with
46-
* `handler`.
47-
*
48-
* A “section” is a heading that passes `test`, until the next heading of the
49-
* same or lower depth, or the end of the document.
50-
* If `ignoreFinalDefinitions: true`, final definitions “in” the section are
51-
* excluded.
52-
*
53-
* @param {Node} tree
54-
* @param {Test|Options} options
55-
* @param {Handler} handler
56-
*/
57-
// eslint-disable-next-line complexity
58-
export function headingRange(tree, options, handler) {
59-
let test = options
60-
/** @type {Array<Node>} */
61-
const children = 'children' in tree ? tree.children : []
62-
/** @type {boolean|undefined} */
63-
let ignoreFinalDefinitions
64-
65-
// Object, not regex.
66-
if (test && typeof test === 'object' && !('exec' in test)) {
67-
ignoreFinalDefinitions = test.ignoreFinalDefinitions
68-
test = test.test
69-
}
70-
71-
// Transform a string into an applicable expression.
72-
if (typeof test === 'string') {
73-
test = new RegExp('^(' + test + ')$', 'i')
74-
}
75-
76-
// Regex.
77-
if (test && 'exec' in test) {
78-
test = wrapExpression(test)
79-
}
80-
81-
if (typeof test !== 'function') {
82-
throw new TypeError(
83-
'Expected `string`, `regexp`, or `function` for `test`, not `' +
84-
test +
85-
'`'
86-
)
87-
}
88-
89-
let index = -1
90-
/** @type {number|undefined} */
91-
let start
92-
/** @type {number|undefined} */
93-
let end
94-
/** @type {number|undefined} */
95-
let depth
96-
97-
// Find the range.
98-
while (++index < children.length) {
99-
const child = children[index]
100-
101-
if (child.type === 'heading') {
102-
if (depth && child.depth <= depth) {
103-
end = index
104-
break
105-
}
106-
107-
if (!depth && test(toString(child), child)) {
108-
depth = child.depth
109-
start = index
110-
// Assume no end heading is found.
111-
end = children.length
112-
}
113-
}
114-
}
115-
116-
// When we have a starting heading.
117-
if (depth && end !== undefined && start !== undefined) {
118-
if (ignoreFinalDefinitions) {
119-
while (
120-
children[end - 1].type === 'definition' ||
121-
children[end - 1].type === 'footnoteDefinition'
122-
) {
123-
end--
124-
}
125-
}
126-
127-
/** @type {Array<Node>} */
128-
const nodes = handler(
129-
// @ts-expect-error `start` points to a heading.
130-
children[start],
131-
children.slice(start + 1, end),
132-
children[end],
133-
{parent: tree, start, end: children[end] ? end : null}
134-
)
135-
136-
if (nodes) {
137-
// Ensure no empty nodes are inserted.
138-
// This could be the case if `end` is in `nodes` but no `end` node exists.
139-
/** @type {Array<Node>} */
140-
const result = []
141-
let index = -1
142-
143-
while (++index < nodes.length) {
144-
if (nodes[index]) result.push(nodes[index])
145-
}
146-
147-
children.splice(start, end - start + 1, ...result)
148-
}
149-
}
150-
}
151-
152-
/**
153-
* Wrap an expression into an assertion function.
154-
* @param {RegExp} expression
155-
* @returns {(value: string) => boolean}
156-
*/
157-
function wrapExpression(expression) {
158-
return assertion
159-
160-
/**
161-
* Assert `value` matches the bound `expression`.
162-
* @param {string} value
163-
* @returns {boolean}
164-
*/
165-
function assertion(value) {
166-
return expression.test(value)
167-
}
168-
}
9+
export {headingRange} from './lib/index.js'

lib/index.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* @typedef {import('unist').Parent} Parent
3+
* @typedef {import('mdast').Root|import('mdast').Content} Node
4+
* @typedef {import('mdast').Heading} Heading
5+
*
6+
* @typedef {(value: string, node: Heading) => boolean} TestFunction
7+
* Function called for each heading with its content and `node` itself check
8+
* if it’s the one to look for.
9+
*
10+
* @typedef {string|RegExp|TestFunction} Test
11+
*
12+
* @typedef Options
13+
* Configuration (optional).
14+
* @property {Test} test
15+
* Heading to look for.
16+
* When `string`, wrapped in `new RegExp('^(' + value + ')$', 'i')`;
17+
* when `RegExp`, wrapped in `function (value) {expression.test(value)}`
18+
* @property {boolean} [ignoreFinalDefinitions=false]
19+
* Ignore final definitions otherwise in the section.
20+
*
21+
* @typedef ZoneInfo
22+
* Extra info.
23+
* @property {Parent|null} parent
24+
* Parent of the range.
25+
* @property {number} start
26+
* index of `start` in `parent`
27+
* @property {number} end
28+
* index of `end` in `parent`
29+
*
30+
* @callback Handler
31+
* Callback called when a range is found.
32+
* @param {Heading|undefined} start
33+
* Start of range.
34+
* @param {Array<Node>} between
35+
* Nodes between `start` and `end`.
36+
* @param {Node|undefined} end
37+
* End of range, if any.
38+
* @param {ZoneInfo} scope
39+
* Extra info.
40+
*/
41+
42+
import {toString} from 'mdast-util-to-string'
43+
44+
/**
45+
* Search `tree` and transform a section without affecting other parts with
46+
* `handler`.
47+
*
48+
* A “section” is a heading that passes `test`, until the next heading of the
49+
* same or lower depth, or the end of the document.
50+
* If `ignoreFinalDefinitions: true`, final definitions “in” the section are
51+
* excluded.
52+
*
53+
* @param {Node} tree
54+
* @param {Test|Options} options
55+
* @param {Handler} handler
56+
*/
57+
// eslint-disable-next-line complexity
58+
export function headingRange(tree, options, handler) {
59+
let test = options
60+
/** @type {Array<Node>} */
61+
const children = 'children' in tree ? tree.children : []
62+
/** @type {boolean|undefined} */
63+
let ignoreFinalDefinitions
64+
65+
// Object, not regex.
66+
if (test && typeof test === 'object' && !('exec' in test)) {
67+
ignoreFinalDefinitions = test.ignoreFinalDefinitions
68+
test = test.test
69+
}
70+
71+
// Transform a string into an applicable expression.
72+
if (typeof test === 'string') {
73+
test = new RegExp('^(' + test + ')$', 'i')
74+
}
75+
76+
// Regex.
77+
if (test && 'exec' in test) {
78+
test = wrapExpression(test)
79+
}
80+
81+
if (typeof test !== 'function') {
82+
throw new TypeError(
83+
'Expected `string`, `regexp`, or `function` for `test`, not `' +
84+
test +
85+
'`'
86+
)
87+
}
88+
89+
let index = -1
90+
/** @type {number|undefined} */
91+
let start
92+
/** @type {number|undefined} */
93+
let end
94+
/** @type {number|undefined} */
95+
let depth
96+
97+
// Find the range.
98+
while (++index < children.length) {
99+
const child = children[index]
100+
101+
if (child.type === 'heading') {
102+
if (depth && child.depth <= depth) {
103+
end = index
104+
break
105+
}
106+
107+
if (!depth && test(toString(child), child)) {
108+
depth = child.depth
109+
start = index
110+
// Assume no end heading is found.
111+
end = children.length
112+
}
113+
}
114+
}
115+
116+
// When we have a starting heading.
117+
if (depth && end !== undefined && start !== undefined) {
118+
if (ignoreFinalDefinitions) {
119+
while (
120+
children[end - 1].type === 'definition' ||
121+
children[end - 1].type === 'footnoteDefinition'
122+
) {
123+
end--
124+
}
125+
}
126+
127+
/** @type {Array<Node>} */
128+
const nodes = handler(
129+
// @ts-expect-error `start` points to a heading.
130+
children[start],
131+
children.slice(start + 1, end),
132+
children[end],
133+
{parent: tree, start, end: children[end] ? end : null}
134+
)
135+
136+
if (nodes) {
137+
// Ensure no empty nodes are inserted.
138+
// This could be the case if `end` is in `nodes` but no `end` node exists.
139+
/** @type {Array<Node>} */
140+
const result = []
141+
let index = -1
142+
143+
while (++index < nodes.length) {
144+
if (nodes[index]) result.push(nodes[index])
145+
}
146+
147+
children.splice(start, end - start + 1, ...result)
148+
}
149+
}
150+
}
151+
152+
/**
153+
* Wrap an expression into an assertion function.
154+
* @param {RegExp} expression
155+
* @returns {(value: string) => boolean}
156+
*/
157+
function wrapExpression(expression) {
158+
return assertion
159+
160+
/**
161+
* Assert `value` matches the bound `expression`.
162+
* @param {string} value
163+
* @returns {boolean}
164+
*/
165+
function assertion(value) {
166+
return expression.test(value)
167+
}
168+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"main": "index.js",
3030
"types": "index.d.ts",
3131
"files": [
32+
"lib/",
3233
"index.d.ts",
3334
"index.js"
3435
],

0 commit comments

Comments
 (0)