Skip to content

Commit 1d9bb05

Browse files
committed
Refactor to externalize core as hast-util-format
1 parent 5cdf41e commit 1d9bb05

File tree

4 files changed

+11
-194
lines changed

4 files changed

+11
-194
lines changed

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @typedef {import('./lib/index.js').Options} Options
2+
* @typedef {import('hast-util-format').Options} Options
33
*/
44

55
export {default} from './lib/index.js'

lib/index.js

Lines changed: 4 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,9 @@
11
/**
2-
* @import {Nodes, RootContent, Root} from 'hast'
2+
* @import {Options} from 'hast-util-format'
3+
* @import {Root} from 'hast'
34
*/
45

5-
/**
6-
* @typedef Options
7-
* Configuration.
8-
* @property {Array<string> | null | undefined} [blanks=[]]
9-
* List of tag names to join with a blank line (default: `[]`); these tags,
10-
* when next to each other, are joined by a blank line (`\n\n`); for example,
11-
* when `['head', 'body']` is given, a blank line is added between these two.
12-
* @property {number | string | null | undefined} [indent=2]
13-
* Indentation per level (default: `2`); when number, uses that amount of
14-
* spaces; when `string`, uses that per indentation level.
15-
* @property {boolean | null | undefined} [indentInitial=true]
16-
* Whether to indent the first level (default: `true`); this is usually the
17-
* `<html>`, thus not indenting `head` and `body`.
18-
*/
19-
20-
import {embedded} from 'hast-util-embedded'
21-
import {isElement} from 'hast-util-is-element'
22-
import {phrasing} from 'hast-util-phrasing'
23-
import {whitespace} from 'hast-util-whitespace'
24-
import {whitespaceSensitiveTagNames} from 'html-whitespace-sensitive-tag-names'
25-
import rehypeMinifyWhitespace from 'rehype-minify-whitespace'
26-
import {SKIP, visitParents} from 'unist-util-visit-parents'
27-
28-
/** @type {Options} */
29-
const emptyOptions = {}
30-
const transformWhitespace = rehypeMinifyWhitespace({newlines: true})
6+
import {format} from 'hast-util-format'
317

328
/**
339
* Format whitespace in HTML.
@@ -38,19 +14,6 @@ const transformWhitespace = rehypeMinifyWhitespace({newlines: true})
3814
* Transform.
3915
*/
4016
export default function rehypeFormat(options) {
41-
const settings = options || emptyOptions
42-
let indent = settings.indent || 2
43-
let indentInitial = settings.indentInitial
44-
45-
if (typeof indent === 'number') {
46-
indent = ' '.repeat(indent)
47-
}
48-
49-
// Default to indenting the initial level.
50-
if (indentInitial === null || indentInitial === undefined) {
51-
indentInitial = true
52-
}
53-
5417
/**
5518
* Transform.
5619
*
@@ -60,150 +23,6 @@ export default function rehypeFormat(options) {
6023
* Nothing.
6124
*/
6225
return function (tree) {
63-
/** @type {boolean | undefined} */
64-
let head
65-
66-
transformWhitespace(tree)
67-
68-
// eslint-disable-next-line complexity
69-
visitParents(tree, function (node, parents) {
70-
let index = -1
71-
72-
if (!('children' in node)) {
73-
return
74-
}
75-
76-
if (isElement(node, 'head')) {
77-
head = true
78-
}
79-
80-
if (head && isElement(node, 'body')) {
81-
head = undefined
82-
}
83-
84-
if (isElement(node, whitespaceSensitiveTagNames)) {
85-
return SKIP
86-
}
87-
88-
const children = node.children
89-
let level = parents.length
90-
91-
// Don’t indent content of whitespace-sensitive nodes / inlines.
92-
if (children.length === 0 || !padding(node, head)) {
93-
return
94-
}
95-
96-
if (!indentInitial) {
97-
level--
98-
}
99-
100-
/** @type {boolean | undefined} */
101-
let eol
102-
103-
// Indent newlines in `text`.
104-
while (++index < children.length) {
105-
const child = children[index]
106-
107-
if (child.type === 'text' || child.type === 'comment') {
108-
if (child.value.includes('\n')) {
109-
eol = true
110-
}
111-
112-
child.value = child.value.replace(
113-
/ *\n/g,
114-
'$&' + String(indent).repeat(level)
115-
)
116-
}
117-
}
118-
119-
/** @type {Array<RootContent>} */
120-
const result = []
121-
/** @type {RootContent | undefined} */
122-
let previous
123-
124-
index = -1
125-
126-
while (++index < children.length) {
127-
const child = children[index]
128-
129-
if (padding(child, head) || (eol && !index)) {
130-
addBreak(result, level, child)
131-
eol = true
132-
}
133-
134-
previous = child
135-
result.push(child)
136-
}
137-
138-
if (previous && (eol || padding(previous, head))) {
139-
// Ignore trailing whitespace (if that already existed), as we’ll add
140-
// properly indented whitespace.
141-
if (whitespace(previous)) {
142-
result.pop()
143-
previous = result[result.length - 1]
144-
}
145-
146-
addBreak(result, level - 1)
147-
}
148-
149-
node.children = result
150-
})
151-
}
152-
153-
/**
154-
* @param {Array<RootContent>} list
155-
* Nodes.
156-
* @param {number} level
157-
* Indentation level.
158-
* @param {RootContent | undefined} [next]
159-
* Next node.
160-
* @returns {undefined}
161-
* Nothing.
162-
*/
163-
function addBreak(list, level, next) {
164-
const tail = list[list.length - 1]
165-
const previous = tail && whitespace(tail) ? list[list.length - 2] : tail
166-
const replace =
167-
(blank(previous) && blank(next) ? '\n\n' : '\n') +
168-
String(indent).repeat(Math.max(level, 0))
169-
170-
if (tail && tail.type === 'text') {
171-
tail.value = whitespace(tail) ? replace : tail.value + replace
172-
} else {
173-
list.push({type: 'text', value: replace})
174-
}
26+
format(tree, options)
17527
}
176-
177-
/**
178-
* @param {Nodes | undefined} node
179-
* Node.
180-
* @returns {boolean}
181-
* Whether `node` is a blank.
182-
*/
183-
function blank(node) {
184-
return Boolean(
185-
node &&
186-
node.type === 'element' &&
187-
settings.blanks &&
188-
settings.blanks.length > 0 &&
189-
settings.blanks.includes(node.tagName)
190-
)
191-
}
192-
}
193-
194-
/**
195-
* @param {Nodes} node
196-
* Node.
197-
* @param {boolean | undefined} head
198-
* Whether the node is in `head`.
199-
* @returns {boolean}
200-
* Whether `node` should be padded.
201-
*/
202-
function padding(node, head) {
203-
return (
204-
node.type === 'root' ||
205-
(node.type === 'element'
206-
? head || isElement(node, 'script') || embedded(node) || !phrasing(node)
207-
: false)
208-
)
20928
}

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@
3535
],
3636
"dependencies": {
3737
"@types/hast": "^3.0.0",
38-
"hast-util-embedded": "^3.0.0",
39-
"hast-util-is-element": "^3.0.0",
40-
"hast-util-phrasing": "^3.0.0",
41-
"hast-util-whitespace": "^3.0.0",
42-
"html-whitespace-sensitive-tag-names": "^3.0.0",
43-
"rehype-minify-whitespace": "^6.0.0",
44-
"unist-util-visit-parents": "^6.0.0"
38+
"hast-util-format": "^1.0.0"
4539
},
4640
"devDependencies": {
4741
"@types/node": "^22.0.0",

readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ This is a rehype plugin that changes whitespace in hast.
5050

5151
This package is useful when you want to improve the readability of HTML source
5252
code as it adds insignificant but pretty whitespace between elements.
53-
A different package, [`rehype-stringify`][rehype-stringify], controls how HTML
53+
The package [`hast-util-format`][hast-util-format] does the same as this plugin
54+
at the utility level.
55+
A different plugin, [`rehype-stringify`][rehype-stringify], controls how HTML
5456
is actually printed: which quotes to use, whether to put a `/` on `<img />`,
5557
etc.
5658
Yet another project, [`rehype-minify`][rehype-minify], does the inverse: improve
@@ -357,6 +359,8 @@ abide by its terms.
357359

358360
[transformer]: https://github.com/unifiedjs/unified#transformer
359361

362+
[hast-util-format]: https://github.com/syntax-tree/hast-util-format
363+
360364
[rehype]: https://github.com/rehypejs/rehype
361365

362366
[rehype-stringify]: https://github.com/rehypejs/rehype/tree/main/packages/rehype-stringify

0 commit comments

Comments
 (0)