UNPKG

remark-lint-no-blockquote-without-marker

Version:

remark-lint rule to warn when block quotes have blank lines without markers

230 lines (210 loc) 6.01 kB
/** * remark-lint rule to warn for lazy lines in block quotes. * * ## What is this? * * This package checks the style of block quotes. * * ## When should I use this? * * You can use this package to check that the style of block quotes is * consistent. * * ## API * * ### `unified().use(remarkLintNoBlockquoteWithoutMarker)` * * Warn for lazy lines in block quotes. * * ###### Parameters * * There are no options. * * ###### Returns * * Transform ([`Transformer` from `unified`][github-unified-transformer]). * * ## Recommendation * * Rules around lazy lines are not straightforward and visually confusing, * so it’s recommended to start each line with a `>`. * * ## Fix * * [`remark-stringify`][github-remark-stringify] adds `>` markers to every line * in a block quote. * * [api-remark-lint-no-blockquote-without-marker]: #unifieduseremarklintnoblockquotewithoutmarker * [github-remark-stringify]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify * [github-unified-transformer]: https://github.com/unifiedjs/unified#transformer * * @module no-blockquote-without-marker * @author Titus Wormer * @copyright Titus Wormer * @license MIT * @example * {"name": "ok.md"} * * > Mercury, * > Venus, * > and Earth. * * Mars. * * @example * {"name": "ok-tabs.md"} * * >␉Mercury, * >␉Venus, * >␉and Earth. * * @example * {"label": "input", "name": "not-ok.md"} * * > Mercury, * Venus, * > and Earth. * @example * {"label": "output", "name": "not-ok.md"} * * 2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * * @example * {"label": "input", "name": "not-ok-tabs.md"} * * >␉Mercury, * ␉Venus, * and Earth. * @example * {"label": "output", "name": "not-ok-tabs.md"} * * 2:2: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * 3:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * * @example * {"label": "input", "name": "containers.md"} * * * > Mercury and * Venus. * * > * Mercury and * Venus. * * * > * Mercury and * Venus. * * > * > Mercury and * Venus. * * *** * * > * > Mercury and * > Venus. * @example * {"label": "output", "name": "containers.md"} * * 2:1: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * 5:3: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * 8:5: Unexpected `0` block quote markers before paragraph line, expected `1` marker, add `1` marker * 11:7: Unexpected `0` block quote markers before paragraph line, expected `2` markers, add `2` markers * 16:7: Unexpected `1` block quote marker before paragraph line, expected `2` markers, add `1` marker */ /** * @import {Root} from 'mdast' * @import {} from 'mdast-util-directive' */ import {ok as assert} from 'devlop' import {phrasing} from 'mdast-util-phrasing' import pluralize from 'pluralize' import {lintRule} from 'unified-lint-rule' import {pointEnd, pointStart} from 'unist-util-position' import {SKIP, visitParents} from 'unist-util-visit-parents' import {location} from 'vfile-location' const remarkLintNoBlockquoteWithoutMarker = lintRule( { origin: 'remark-lint:no-blockquote-without-marker', url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-blockquote-without-marker#readme' }, /** * @param {Root} tree * Tree. * @returns {undefined} * Nothing. */ function (tree, file) { const value = String(file) const loc = location(file) // Only paragraphs can be lazy. visitParents(tree, function (node, parents) { // Do not walk into phrasing. if (phrasing(node)) { return SKIP } if (node.type !== 'paragraph') return let expected = 0 for (const parent of parents) { if (parent.type === 'blockquote') { expected++ } // All known parents that only use whitespace for indent. else if ( parent.type === 'containerDirective' || parent.type === 'footnoteDefinition' || parent.type === 'list' || parent.type === 'listItem' || parent.type === 'root' ) { // Empty. /* c8 ignore next 3 -- exit on unknown nodes. */ } else { return SKIP } } if (!expected) return SKIP const end = pointEnd(node) const start = pointStart(node) if (!end || !start) return SKIP let line = start.line while (++line <= end.line) { // Skip first line. const lineStart = loc.toOffset({line, column: 1}) assert(lineStart !== undefined) // Always defined. let actual = 0 let index = lineStart while (index < value.length) { const code = value.charCodeAt(index) if (code === 9 || code === 32) { // Fine. } else if (code === 62 /* `>` */) { actual++ } else { break } index++ } const point = loc.toPoint(index) assert(point) // Always defined. const difference = expected - actual // Roughly here. if (difference) { file.message( 'Unexpected `' + actual + '` block quote ' + pluralize('marker', actual) + ' before paragraph line, expected `' + expected + '` ' + pluralize('marker', expected) + ', add `' + difference + '` ' + pluralize('marker', difference), {ancestors: [...parents, node], place: point} ) } } }) } ) export default remarkLintNoBlockquoteWithoutMarker