Files
coopgo/node_modules/rehype-minify-whitespace/index.js
sgauthier 6e64e138e2
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
planning
2024-10-14 09:15:30 +02:00

254 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @fileoverview
* Collapse whitespace.
*
* Normally, collapses to a single space.
* If `newlines: true`, collapses whitespace containing newlines to `'\n'`
* instead of `' '`.
* @example
* <h1>Heading</h1>
* <p><strong>This</strong> and <em>that</em></p>
*/
'use strict'
var is = require('hast-util-is-element')
var embedded = require('hast-util-embedded')
var convert = require('unist-util-is/convert')
var whitespace = require('hast-util-whitespace')
var blocks = require('./block')
var contents = require('./content')
var skippables = require('./skippable')
module.exports = minifyWhitespace
var ignorableNode = convert(['doctype', 'comment'])
var parent = convert(['element', 'root'])
var root = convert(['root'])
var element = convert(['element'])
var text = convert(['text'])
function minifyWhitespace(options) {
var collapse = collapseFactory(
(options || {}).newlines ? replaceNewlines : replaceWhitespace
)
return transform
function transform(tree) {
minify(tree, {collapse: collapse, whitespace: 'normal'})
}
}
function minify(node, options) {
var settings
if (parent(node)) {
settings = Object.assign({}, options)
if (root(node) || blocklike(node)) {
settings.before = true
settings.after = true
}
settings.whitespace = inferWhiteSpace(node, options)
return all(node, settings)
}
if (text(node)) {
if (options.whitespace === 'normal') {
return minifyText(node, options)
}
// Naïve collapse, but no trimming:
if (options.whitespace === 'nowrap') {
node.value = options.collapse(node.value)
}
// The `pre-wrap` or `pre` whitespace settings are neither collapsed nor
// trimmed.
}
return {
remove: false,
ignore: ignorableNode(node),
stripAtStart: false
}
}
function minifyText(node, options) {
var value = options.collapse(node.value)
var start = 0
var end = value.length
var result = {remove: false, ignore: false, stripAtStart: false}
if (options.before && removable(value.charAt(0))) {
start++
}
if (start !== end && removable(value.charAt(end - 1))) {
if (options.after) {
end--
} else {
result.stripAtStart = true
}
}
if (start === end) {
result.remove = true
} else {
node.value = value.slice(start, end)
}
return result
}
function all(parent, options) {
var before = options.before
var after = options.after
var children = parent.children
var length = children.length
var index = -1
var result
while (++index < length) {
result = minify(
children[index],
Object.assign({}, options, {
before: before,
after: collapsableAfter(children, index, after)
})
)
if (result.remove) {
children.splice(index, 1)
index--
length--
} else if (!result.ignore) {
before = result.stripAtStart
}
// If this element, such as a `<select>` or `<img>`, contributes content
// somehow, allow whitespace again.
if (content(children[index])) {
before = false
}
}
return {
remove: false,
ignore: false,
stripAtStart: before || after
}
}
function collapsableAfter(nodes, index, after) {
var length = nodes.length
var node
var result
while (++index < length) {
node = nodes[index]
result = inferBoundary(node)
if (result === undefined && node.children && !skippable(node)) {
result = collapsableAfter(node.children, -1)
}
if (typeof result === 'boolean') {
return result
}
}
return after
}
// Infer two types of boundaries:
//
// 1. `true` — boundary for which whitespace around it does not contribute
// anything
// 2. `false` — boundary for which whitespace around it *does* contribute
//
// No result (`undefined`) is returned if it is unknown.
function inferBoundary(node) {
if (element(node)) {
if (content(node)) {
return false
}
if (blocklike(node)) {
return true
}
// Unknown: either depends on siblings if embedded or metadata, or on
// children.
} else if (text(node)) {
if (!whitespace(node)) {
return false
}
} else if (!ignorableNode(node)) {
return false
}
}
// Infer whether a node is skippable.
function content(node) {
return embedded(node) || is(node, contents)
}
// See: <https://html.spec.whatwg.org/#the-css-user-agent-style-sheet-and-presentational-hints>
function blocklike(node) {
return is(node, blocks)
}
function skippable(node) {
/* istanbul ignore next - currently only used on elements, but just to make sure. */
var props = node.properties || {}
return ignorableNode(node) || is(node, skippables) || props.hidden
}
function removable(character) {
return character === ' ' || character === '\n'
}
function replaceNewlines(value) {
var match = /\r?\n|\r/.exec(value)
return match ? match[0] : ' '
}
function replaceWhitespace() {
return ' '
}
function collapseFactory(replace) {
return collapse
function collapse(value) {
return String(value).replace(/[\t\n\v\f\r ]+/g, replace)
}
}
// We dont support void elements here (so `nobr wbr` -> `normal` is ignored).
function inferWhiteSpace(node, options) {
var props = node.properties || {}
switch (node.tagName) {
case 'listing':
case 'plaintext':
case 'xmp':
return 'pre'
case 'nobr':
return 'nowrap'
case 'pre':
return props.wrap ? 'pre-wrap' : 'pre'
case 'td':
case 'th':
return props.noWrap ? 'nowrap' : options.whitespace
case 'textarea':
return 'pre-wrap'
default:
return options.whitespace
}
}