360 lines
8.6 KiB
JavaScript
360 lines
8.6 KiB
JavaScript
/**
|
|
* @typedef {import('hast').Element} Element
|
|
* @typedef {import('hast').Parents} Parents
|
|
*/
|
|
|
|
import {whitespace} from 'hast-util-whitespace'
|
|
import {siblingAfter} from './util/siblings.js'
|
|
import {omission} from './omission.js'
|
|
|
|
export const closing = omission({
|
|
body,
|
|
caption: headOrColgroupOrCaption,
|
|
colgroup: headOrColgroupOrCaption,
|
|
dd,
|
|
dt,
|
|
head: headOrColgroupOrCaption,
|
|
html,
|
|
li,
|
|
optgroup,
|
|
option,
|
|
p,
|
|
rp: rubyElement,
|
|
rt: rubyElement,
|
|
tbody,
|
|
td: cells,
|
|
tfoot,
|
|
th: cells,
|
|
thead,
|
|
tr
|
|
})
|
|
|
|
/**
|
|
* Macro for `</head>`, `</colgroup>`, and `</caption>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function headOrColgroupOrCaption(_, index, parent) {
|
|
const next = siblingAfter(parent, index, true)
|
|
return (
|
|
!next ||
|
|
(next.type !== 'comment' &&
|
|
!(next.type === 'text' && whitespace(next.value.charAt(0))))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</html>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function html(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return !next || next.type !== 'comment'
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</body>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function body(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return !next || next.type !== 'comment'
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</p>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function p(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return next
|
|
? next.type === 'element' &&
|
|
(next.tagName === 'address' ||
|
|
next.tagName === 'article' ||
|
|
next.tagName === 'aside' ||
|
|
next.tagName === 'blockquote' ||
|
|
next.tagName === 'details' ||
|
|
next.tagName === 'div' ||
|
|
next.tagName === 'dl' ||
|
|
next.tagName === 'fieldset' ||
|
|
next.tagName === 'figcaption' ||
|
|
next.tagName === 'figure' ||
|
|
next.tagName === 'footer' ||
|
|
next.tagName === 'form' ||
|
|
next.tagName === 'h1' ||
|
|
next.tagName === 'h2' ||
|
|
next.tagName === 'h3' ||
|
|
next.tagName === 'h4' ||
|
|
next.tagName === 'h5' ||
|
|
next.tagName === 'h6' ||
|
|
next.tagName === 'header' ||
|
|
next.tagName === 'hgroup' ||
|
|
next.tagName === 'hr' ||
|
|
next.tagName === 'main' ||
|
|
next.tagName === 'menu' ||
|
|
next.tagName === 'nav' ||
|
|
next.tagName === 'ol' ||
|
|
next.tagName === 'p' ||
|
|
next.tagName === 'pre' ||
|
|
next.tagName === 'section' ||
|
|
next.tagName === 'table' ||
|
|
next.tagName === 'ul')
|
|
: !parent ||
|
|
// Confusing parent.
|
|
!(
|
|
parent.type === 'element' &&
|
|
(parent.tagName === 'a' ||
|
|
parent.tagName === 'audio' ||
|
|
parent.tagName === 'del' ||
|
|
parent.tagName === 'ins' ||
|
|
parent.tagName === 'map' ||
|
|
parent.tagName === 'noscript' ||
|
|
parent.tagName === 'video')
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</li>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function li(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return !next || (next.type === 'element' && next.tagName === 'li')
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</dt>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function dt(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return Boolean(
|
|
next &&
|
|
next.type === 'element' &&
|
|
(next.tagName === 'dt' || next.tagName === 'dd')
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</dd>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function dd(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return (
|
|
!next ||
|
|
(next.type === 'element' &&
|
|
(next.tagName === 'dt' || next.tagName === 'dd'))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</rt>` or `</rp>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function rubyElement(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return (
|
|
!next ||
|
|
(next.type === 'element' &&
|
|
(next.tagName === 'rp' || next.tagName === 'rt'))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</optgroup>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function optgroup(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return !next || (next.type === 'element' && next.tagName === 'optgroup')
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</option>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function option(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return (
|
|
!next ||
|
|
(next.type === 'element' &&
|
|
(next.tagName === 'option' || next.tagName === 'optgroup'))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</thead>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function thead(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return Boolean(
|
|
next &&
|
|
next.type === 'element' &&
|
|
(next.tagName === 'tbody' || next.tagName === 'tfoot')
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</tbody>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function tbody(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return (
|
|
!next ||
|
|
(next.type === 'element' &&
|
|
(next.tagName === 'tbody' || next.tagName === 'tfoot'))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</tfoot>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function tfoot(_, index, parent) {
|
|
return !siblingAfter(parent, index)
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</tr>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function tr(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return !next || (next.type === 'element' && next.tagName === 'tr')
|
|
}
|
|
|
|
/**
|
|
* Whether to omit `</td>` or `</th>`.
|
|
*
|
|
* @param {Element} _
|
|
* Element.
|
|
* @param {number | undefined} index
|
|
* Index of element in parent.
|
|
* @param {Parents | undefined} parent
|
|
* Parent of element.
|
|
* @returns {boolean}
|
|
* Whether the closing tag can be omitted.
|
|
*/
|
|
function cells(_, index, parent) {
|
|
const next = siblingAfter(parent, index)
|
|
return (
|
|
!next ||
|
|
(next.type === 'element' &&
|
|
(next.tagName === 'td' || next.tagName === 'th'))
|
|
)
|
|
}
|