remark-lint-no-missing-blank-lines
Version:
remark-lint rule to warn when missing blank lines
277 lines (264 loc) • 7.04 kB
JavaScript
/**
* remark-lint rule to warn when blank lines are missing.
*
* ## What is this?
*
* This package checks missing blank lines.
*
* ## When should I use this?
*
* You can use this package to check blank lines.
*
* ## API
*
* ### `unified().use(remarkLintNoMissingBlankLines[, options])`
*
* Warn when blank lines are missing.
*
* ###### Parameters
*
* * `options` ([`Options`][api-options], optional)
* — configuration
*
* ###### Returns
*
* Transform ([`Transformer` from `unified`][github-unified-transformer]).
*
* ### `Options`
*
* Configuration (TypeScript type).
*
* ###### Fields
*
* * `exceptTightLists` (`boolean`, default: `false`)
* — allow omitting blank lines in list items
*
* ## Recommendation
*
* Blank lines are required in certain sometimes confusing cases.
* So it’s recommended to always use blank lines between blocks.
*
* ## Fix
*
* [`remark-stringify`][github-remark-stringify] always uses blank lines
* between blocks.
* It has a `join` function to customize such behavior.
*
* [api-options]: #options
* [api-remark-lint-no-missing-blank-lines]: #unifieduseremarklintnomissingblanklines-options
* [github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify
* [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer
*
* @module no-missing-blank-lines
* @author Titus Wormer
* @copyright Titus Wormer
* @license MIT
*
* @example
* {"name": "ok.md"}
*
* # Mercury
*
* ## Venus
*
* * Earth.
*
* * Mars.
*
* > # Jupiter
* >
* > Saturn.
*
* @example
* {"label": "input", "name": "not-ok.md"}
*
* # Mercury
* ## Venus
*
* * Earth
* * Mars.
*
* > # Jupiter
* > Saturn.
* @example
* {"label": "output", "name": "not-ok.md"}
*
* 2:1-2:9: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
* 5:3-5:10: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
* 8:3-8:10: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*
* @example
* {"config": {"exceptTightLists": true}, "name": "tight.md"}
*
* * Venus.
*
* * Mars.
*
* @example
* {"label": "input", "name": "containers.md"}
*
* > # Venus
* >
* > Mercury.
*
* - earth.
* - mars.
*
* * # Jupiter
* Saturn.
* @example
* {"label": "output", "name": "containers.md"}
*
* 9:3-9:10: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*
* @example
* {"gfm": true, "label": "input", "name": "gfm.md"}
*
* | Planet | Diameter |
* | ------- | -------- |
* | Mercury | 4 880 km |
*
* [^Mercury]:
* **Mercury** is the first planet from the Sun and the smallest
* in the Solar System.
* [^Venus]:
* **Venus** is the second planet from the Sun.
* @example
* {"gfm": true, "label": "output", "name": "gfm.md"}
*
* 8:1-9:49: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*
* @example
* {"label": "input", "mdx": true, "name": "mdx.mdx"}
*
* <Tip kind="info">
* # Venus
* Mars.
* </Tip>
* {Math.PI}
* @example
* {"label": "output", "mdx": true, "name": "mdx.mdx"}
*
* 3:3-3:8: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
* 5:1-5:10: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*
* @example
* {"label": "input", "math": true, "name": "math.md"}
*
* $$
* \frac{1}{2}
* $$
* $$
* \frac{2}{3}
* $$
* @example
* {"label": "output", "math": true, "name": "math.md"}
*
* 4:1-6:3: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*
* @example
* {"directive": true, "label": "input", "name": "directive.md"}
*
* Directives are also checked.
*
* ::video{#mercury}
* :::planet
* Venus.
* :::
* @example
* {"directive": true, "label": "output", "name": "directive.md"}
*
* 4:1-6:4: Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line
*/
/**
* @import {Nodes, Root} from 'mdast'
* @import {} from 'mdast-util-directive'
* @import {} from 'mdast-util-math'
* @import {} from 'mdast-util-mdx'
*/
/**
* @typedef Options
* Configuration.
* @property {boolean | null | undefined} [exceptTightLists=false]
* Allow tight list items (default: `false`).
*/
import {phrasing} from 'mdast-util-phrasing'
import {lintRule} from 'unified-lint-rule'
import {pointEnd, pointStart} from 'unist-util-position'
import {SKIP, visitParents} from 'unist-util-visit-parents'
/** @type {ReadonlyArray<Nodes['type']>} */
// eslint-disable-next-line unicorn/prefer-set-has
const types = [
'blockquote',
'code',
'containerDirective',
'definition',
'footnoteDefinition',
'heading',
'html',
'leafDirective',
'list',
'math',
'mdxFlowExpression',
'mdxJsxFlowElement',
'paragraph',
'table',
'thematicBreak',
// @ts-expect-error: `remark-frontmatter`.
'toml',
'yaml'
]
const remarkLintNoMissingBlankLines = lintRule(
{
origin: 'remark-lint:no-missing-blank-lines',
url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-missing-blank-lines#readme'
},
/**
* @param {Root} tree
* Tree.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {undefined}
* Nothing.
*/
function (tree, file, options) {
const exceptTightLists = options ? options.exceptTightLists : false
visitParents(tree, function (node, parents) {
const parent = parents[parents.length - 1]
if (!parent) return
// Do not walk into phrasing.
if (phrasing(node)) {
return SKIP
}
// To do: minor: add `exceptDefinitions` option?
if (
// To do: next major: allow tight lists by default.
// Children of list items are normally checked.
(!exceptTightLists || parent.type !== 'listItem') &&
// Known block:
types.includes(node.type)
) {
const start = pointStart(node)
const siblings = /** @type {Array<Nodes>} */ (parent.children)
const previous = siblings[siblings.indexOf(node) - 1]
const previousEnd = pointEnd(previous)
if (
!previous ||
!previousEnd ||
!start ||
// Other known block:
!types.includes(previous.type)
) {
return
}
if (previousEnd.line + 1 === start.line) {
file.message(
'Unexpected `0` blank lines between nodes, expected `1` or more blank lines, add `1` blank line',
{ancestors: [...parents, node], place: node.position}
)
}
}
})
}
)
export default remarkLintNoMissingBlankLines