From f31b0c9700471b937b7f44caf1dc0b42408a17ad Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Fri, 1 Oct 2021 07:31:34 -0700 Subject: [PATCH] tools: update remark-preset-lint-node to 3.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/40278 Reviewed-By: Antoine du Hamel Reviewed-By: Luigi Pinca Reviewed-By: Michaël Zasso --- tools/lint-md/lint-md.mjs | 24388 +++++++++++++++--------------- tools/lint-md/lint-md.src.mjs | 2 - tools/lint-md/package-lock.json | 551 +- tools/lint-md/package.json | 1 - 4 files changed, 12328 insertions(+), 12614 deletions(-) diff --git a/tools/lint-md/lint-md.mjs b/tools/lint-md/lint-md.mjs index fb02f6c5025bd3..af678e476c23d2 100644 --- a/tools/lint-md/lint-md.mjs +++ b/tools/lint-md/lint-md.mjs @@ -12505,6 +12505,7 @@ function toResult$1(value) { * @typedef {import('unist').Parent} Parent * @typedef {import('unist-util-is').Test} Test * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult + * @typedef {import('./complex-types').Visitor} Visitor */ /** @@ -12518,15 +12519,15 @@ function toResult$1(value) { const visit$1 = /** * @type {( - * ((tree: Tree, test: Check, visitor: Visitor, Check>>, reverse?: boolean) => void) & - * ((tree: Tree, visitor: Visitor>, reverse?: boolean) => void) + * ((tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) & + * ((tree: Tree, visitor: import('./complex-types').BuildVisitor, reverse?: boolean) => void) * )} */ ( /** * @param {Node} tree * @param {Test} test - * @param {Visitor} visitor + * @param {import('./complex-types').Visitor} visitor * @param {boolean} [reverse] */ function (tree, test, visitor, reverse) { @@ -13784,14496 +13785,14548 @@ function remarkStringify(options) { } /** - * @typedef {import('unist').Point} Point - * @typedef {import('vfile').VFile} VFile - * - * @typedef {Pick} PositionalPoint - * @typedef {Required} FullPoint - * @typedef {NonNullable} Offset - */ - -/** - * Get transform functions for the given `document`. - * - * @param {string|Uint8Array|VFile} file + * @typedef {import('micromark-util-types').Extension} Extension + * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord + * @typedef {import('micromark-util-types').Tokenizer} Tokenizer + * @typedef {import('micromark-util-types').Previous} Previous + * @typedef {import('micromark-util-types').State} State + * @typedef {import('micromark-util-types').Event} Event + * @typedef {import('micromark-util-types').Code} Code */ -function location(file) { - var value = String(file); - /** @type {Array.} */ - var indices = []; - var search = /\r?\n|\r/g; +const www = { + tokenize: tokenizeWww, + partial: true +}; +const domain = { + tokenize: tokenizeDomain, + partial: true +}; +const path = { + tokenize: tokenizePath, + partial: true +}; +const punctuation = { + tokenize: tokenizePunctuation, + partial: true +}; +const namedCharacterReference = { + tokenize: tokenizeNamedCharacterReference, + partial: true +}; +const wwwAutolink = { + tokenize: tokenizeWwwAutolink, + previous: previousWww +}; +const httpAutolink = { + tokenize: tokenizeHttpAutolink, + previous: previousHttp +}; +const emailAutolink = { + tokenize: tokenizeEmailAutolink, + previous: previousEmail +}; +/** @type {ConstructRecord} */ - while (search.test(value)) { - indices.push(search.lastIndex); - } +const text = {}; +/** @type {Extension} */ - indices.push(value.length + 1); +const gfmAutolinkLiteral = { + text +}; +let code = 48; // Add alphanumerics. - return {toPoint, toOffset} +while (code < 123) { + text[code] = emailAutolink; + code++; + if (code === 58) code = 65; + else if (code === 91) code = 97; +} - /** - * Get the line and column-based `point` for `offset` in the bound indices. - * Returns a point with `undefined` values when given invalid or out of bounds - * input. - * - * @param {Offset} offset - * @returns {FullPoint} - */ - function toPoint(offset) { - var index = -1; +text[43] = emailAutolink; +text[45] = emailAutolink; +text[46] = emailAutolink; +text[95] = emailAutolink; +text[72] = [emailAutolink, httpAutolink]; +text[104] = [emailAutolink, httpAutolink]; +text[87] = [emailAutolink, wwwAutolink]; +text[119] = [emailAutolink, wwwAutolink]; +/** @type {Tokenizer} */ - if (offset > -1 && offset < indices[indices.length - 1]) { - while (++index < indices.length) { - if (indices[index] > offset) { - return { - line: index + 1, - column: offset - (indices[index - 1] || 0) + 1, - offset - } - } - } - } +function tokenizeEmailAutolink(effects, ok, nok) { + const self = this; + /** @type {boolean} */ - return {line: undefined, column: undefined, offset: undefined} - } + let hasDot; + /** @type {boolean|undefined} */ - /** - * Get the `offset` for a line and column-based `point` in the bound indices. - * Returns `-1` when given invalid or out of bounds input. - * - * @param {PositionalPoint} point - * @returns {Offset} - */ - function toOffset(point) { - var line = point && point.line; - var column = point && point.column; - /** @type {number} */ - var offset; + let hasDigitInLastSegment; + return start + /** @type {State} */ + function start(code) { if ( - typeof line === 'number' && - typeof column === 'number' && - !Number.isNaN(line) && - !Number.isNaN(column) && - line - 1 in indices + !gfmAtext(code) || + !previousEmail(self.previous) || + previousUnbalanced(self.events) ) { - offset = (indices[line - 2] || 0) + column - 1 || 0; + return nok(code) } - return offset > -1 && offset < indices[indices.length - 1] ? offset : -1 + effects.enter('literalAutolink'); + effects.enter('literalAutolinkEmail'); + return atext(code) } -} + /** @type {State} */ -/** - * @param {string} d - * @returns {string} - */ -function color(d) { - return '\u001B[33m' + d + '\u001B[39m' -} + function atext(code) { + if (gfmAtext(code)) { + effects.consume(code); + return atext + } -/** - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent - * @typedef {import('unist-util-is').Test} Test - */ + if (code === 64) { + effects.consume(code); + return label + } -/** - * Continue traversing as normal - */ -const CONTINUE = true; -/** - * Do not traverse this node’s children - */ -const SKIP = 'skip'; -/** - * Stop traversing immediately - */ -const EXIT = false; + return nok(code) + } + /** @type {State} */ -const visitParents = - /** - * @type {( - * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, test: Test, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) - * )} - */ - ( - /** - * Visit children of tree which pass a test - * - * @param {Node} tree Abstract syntax tree to walk - * @param {Test} test test Test node - * @param {Visitor} visitor Function to run for each node - * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false - */ - function (tree, test, visitor, reverse) { - if (typeof test === 'function' && typeof visitor !== 'function') { - reverse = visitor; - // @ts-ignore no visitor given, so `visitor` is test. - visitor = test; - test = null; + function label(code) { + if (code === 46) { + return effects.check(punctuation, done, dotContinuation)(code) + } + + if (code === 45 || code === 95) { + return effects.check(punctuation, nok, dashOrUnderscoreContinuation)(code) + } + + if (asciiAlphanumeric(code)) { + if (!hasDigitInLastSegment && asciiDigit(code)) { + hasDigitInLastSegment = true; } - var is = convert(test); - var step = reverse ? -1 : 1; + effects.consume(code); + return label + } - factory(tree, null, [])(); + return done(code) + } + /** @type {State} */ - /** - * @param {Node} node - * @param {number?} index - * @param {Array.} parents - */ - function factory(node, index, parents) { - /** @type {Object.} */ - var value = typeof node === 'object' && node !== null ? node : {}; - /** @type {string} */ - var name; + function dotContinuation(code) { + effects.consume(code); + hasDot = true; + hasDigitInLastSegment = undefined; + return label + } + /** @type {State} */ - if (typeof value.type === 'string') { - name = - typeof value.tagName === 'string' - ? value.tagName - : typeof value.name === 'string' - ? value.name - : undefined; + function dashOrUnderscoreContinuation(code) { + effects.consume(code); + return afterDashOrUnderscore + } + /** @type {State} */ - Object.defineProperty(visit, 'name', { - value: - 'node (' + - color(value.type + (name ? '<' + name + '>' : '')) + - ')' - }); - } + function afterDashOrUnderscore(code) { + if (code === 46) { + return effects.check(punctuation, nok, dotContinuation)(code) + } - return visit + return label(code) + } + /** @type {State} */ - function visit() { - /** @type {ActionTuple} */ - var result = []; - /** @type {ActionTuple} */ - var subresult; - /** @type {number} */ - var offset; - /** @type {Array.} */ - var grandparents; + function done(code) { + if (hasDot && !hasDigitInLastSegment) { + effects.exit('literalAutolinkEmail'); + effects.exit('literalAutolink'); + return ok(code) + } - if (!test || is(node, index, parents[parents.length - 1] || null)) { - result = toResult(visitor(node, parents)); + return nok(code) + } +} +/** @type {Tokenizer} */ - if (result[0] === EXIT) { - return result - } - } +function tokenizeWwwAutolink(effects, ok, nok) { + const self = this; + return start + /** @type {State} */ - if (node.children && result[0] !== SKIP) { - // @ts-ignore looks like a parent. - offset = (reverse ? node.children.length : -1) + step; - // @ts-ignore looks like a parent. - grandparents = parents.concat(node); + function start(code) { + if ( + (code !== 87 && code !== 119) || + !previousWww(self.previous) || + previousUnbalanced(self.events) + ) { + return nok(code) + } - // @ts-ignore looks like a parent. - while (offset > -1 && offset < node.children.length) { - subresult = factory(node.children[offset], offset, grandparents)(); + effects.enter('literalAutolink'); + effects.enter('literalAutolinkWww'); // For `www.` we check instead of attempt, because when it matches, GH + // treats it as part of a domain (yes, it says a valid domain must come + // after `www.`, but that’s not how it’s implemented by them). - if (subresult[0] === EXIT) { - return subresult - } + return effects.check( + www, + effects.attempt(domain, effects.attempt(path, done), nok), + nok + )(code) + } + /** @type {State} */ - offset = - typeof subresult[1] === 'number' ? subresult[1] : offset + step; - } - } + function done(code) { + effects.exit('literalAutolinkWww'); + effects.exit('literalAutolink'); + return ok(code) + } +} +/** @type {Tokenizer} */ - return result - } - } +function tokenizeHttpAutolink(effects, ok, nok) { + const self = this; + return start + /** @type {State} */ + + function start(code) { + if ( + (code !== 72 && code !== 104) || + !previousHttp(self.previous) || + previousUnbalanced(self.events) + ) { + return nok(code) } - ); -/** - * @param {VisitorResult} value - * @returns {ActionTuple} - */ -function toResult(value) { - if (Array.isArray(value)) { - return value + effects.enter('literalAutolink'); + effects.enter('literalAutolinkHttp'); + effects.consume(code); + return t1 } + /** @type {State} */ - if (typeof value === 'number') { - return [CONTINUE, value] + function t1(code) { + if (code === 84 || code === 116) { + effects.consume(code); + return t2 + } + + return nok(code) } + /** @type {State} */ - return [value] -} + function t2(code) { + if (code === 84 || code === 116) { + effects.consume(code); + return p + } -/** - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent - * @typedef {import('unist-util-is').Test} Test - * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult - */ + return nok(code) + } + /** @type {State} */ -const visit = - /** - * @type {( - * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, test: Test, visitor: Visitor, reverse?: boolean) => void) & - * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) - * )} - */ - ( - /** - * Visit children of tree which pass a test - * - * @param {Node} tree Abstract syntax tree to walk - * @param {Test} test test Test node - * @param {Visitor} visitor Function to run for each node - * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false - */ - function (tree, test, visitor, reverse) { - if (typeof test === 'function' && typeof visitor !== 'function') { - reverse = visitor; - visitor = test; - test = null; - } + function p(code) { + if (code === 80 || code === 112) { + effects.consume(code); + return s + } - visitParents(tree, test, overload, reverse); + return nok(code) + } + /** @type {State} */ - /** - * @param {Node} node - * @param {Array.} parents - */ - function overload(node, parents) { - var parent = parents[parents.length - 1]; - return visitor( - node, - parent ? parent.children.indexOf(node) : null, - parent - ) - } + function s(code) { + if (code === 83 || code === 115) { + effects.consume(code); + return colon } - ); -/** - * @typedef {import('unist').Node} Node - * @typedef {import('unist').Parent} Parent - * @typedef {import('unist').Point} Point - * @typedef {import('unist-util-is').Test} Test - * @typedef {import('vfile').VFile} VFile - * @typedef {import('vfile-message').VFileMessage} VFileMessage - * - * @typedef {OptionsWithoutReset|OptionsWithReset} Options - * @typedef {OptionsBaseFields & OptionsWithoutResetFields} OptionsWithoutReset - * @typedef {OptionsBaseFields & OptionsWithResetFields} OptionsWithReset - * - * @typedef OptionsWithoutResetFields - * @property {false} [reset] - * Whether to treat all messages as turned off initially. - * @property {string[]} [disable] - * List of `ruleId`s to turn off. - * - * @typedef OptionsWithResetFields - * @property {true} reset - * Whether to treat all messages as turned off initially. - * @property {string[]} [enable] - * List of `ruleId`s to initially turn on. - * - * @typedef OptionsBaseFields - * @property {string} name - * Name of markers that can control the message sources. - * - * For example, `{name: 'alpha'}` controls `alpha` markers: - * - * ```html - * - * ``` - * @property {MarkerParser} marker - * Parse a possible marker to a comment marker object (Marker). - * If the marker isn't a marker, should return `null`. - * @property {Test} [test] - * Test for possible markers - * @property {string[]} [known] - * List of allowed `ruleId`s. When given a warning is shown - * when someone tries to control an unknown rule. - * - * For example, `{name: 'alpha', known: ['bravo']}` results in a warning if - * `charlie` is configured: - * - * ```html - * - * ``` - * @property {string|string[]} [source] - * Sources that can be controlled with `name` markers. - * Defaults to `name`. - * - * @callback MarkerParser - * Parse a possible comment marker node to a Marker. - * @param {Node} node - * Node to parse - * - * @typedef Marker - * A comment marker. - * @property {string} name - * Name of marker. - * @property {string} attributes - * Value after name. - * @property {Record} parameters - * Parsed attributes. - * @property {Node} node - * Reference to given node. - * - * @typedef Mark - * @property {Point|undefined} point - * @property {boolean} state - */ - -const own$3 = {}.hasOwnProperty; - -/** - * @type {import('unified').Plugin<[Options]>} - * @returns {(tree: Node, file: VFile) => void} - */ -function messageControl(options) { - if (!options || typeof options !== 'object' || !options.name) { - throw new Error( - 'Expected `name` in `options`, got `' + (options || {}).name + '`' - ) + return colon(code) } + /** @type {State} */ - if (!options.marker) { - throw new Error( - 'Expected `marker` in `options`, got `' + options.marker + '`' - ) + function colon(code) { + if (code === 58) { + effects.consume(code); + return slash1 + } + + return nok(code) } + /** @type {State} */ - const enable = 'enable' in options && options.enable ? options.enable : []; - const disable = 'disable' in options && options.disable ? options.disable : []; - let reset = options.reset; - const sources = - typeof options.source === 'string' - ? [options.source] - : options.source || [options.name]; + function slash1(code) { + if (code === 47) { + effects.consume(code); + return slash2 + } - return transformer + return nok(code) + } + /** @type {State} */ - /** - * @param {Node} tree - * @param {VFile} file - */ - function transformer(tree, file) { - const toOffset = location(file).toOffset; - const initial = !reset; - const gaps = detectGaps(tree, file); - /** @type {Record} */ - const scope = {}; - /** @type {Mark[]} */ - const globals = []; + function slash2(code) { + if (code === 47) { + effects.consume(code); + return after + } - visit(tree, options.test, visitor); + return nok(code) + } + /** @type {State} */ - file.messages = file.messages.filter((m) => filter(m)); + function after(code) { + return code === null || + asciiControl(code) || + unicodeWhitespace(code) || + unicodePunctuation(code) + ? nok(code) + : effects.attempt(domain, effects.attempt(path, done), nok)(code) + } + /** @type {State} */ - /** - * @param {Node} node - * @param {number|null} position - * @param {Parent|null} parent - */ - function visitor(node, position, parent) { - /** @type {Marker|null} */ - const mark = options.marker(node); + function done(code) { + effects.exit('literalAutolinkHttp'); + effects.exit('literalAutolink'); + return ok(code) + } +} +/** @type {Tokenizer} */ - if (!mark || mark.name !== options.name) { - return - } +function tokenizeWww(effects, ok, nok) { + return start + /** @type {State} */ - const ruleIds = mark.attributes.split(/\s/g); - const point = mark.node.position && mark.node.position.start; - const next = - (parent && position !== null && parent.children[position + 1]) || - undefined; - const tail = (next && next.position && next.position.end) || undefined; - let index = -1; + function start(code) { + effects.consume(code); + return w2 + } + /** @type {State} */ - /** @type {string} */ - // @ts-expect-error: we’ll check for unknown values next. - const verb = ruleIds.shift(); + function w2(code) { + if (code === 87 || code === 119) { + effects.consume(code); + return w3 + } - if (verb !== 'enable' && verb !== 'disable' && verb !== 'ignore') { - file.fail( - 'Unknown keyword `' + - verb + - '`: expected ' + - "`'enable'`, `'disable'`, or `'ignore'`", - mark.node - ); - } + return nok(code) + } + /** @type {State} */ - // Apply to all rules. - if (ruleIds.length > 0) { - while (++index < ruleIds.length) { - const ruleId = ruleIds[index]; + function w3(code) { + if (code === 87 || code === 119) { + effects.consume(code); + return dot + } - if (isKnown(ruleId, verb, mark.node)) { - toggle(point, verb === 'enable', ruleId); + return nok(code) + } + /** @type {State} */ - if (verb === 'ignore') { - toggle(tail, true, ruleId); - } - } - } - } else if (verb === 'ignore') { - toggle(point, false); - toggle(tail, true); - } else { - toggle(point, verb === 'enable'); - reset = verb !== 'enable'; - } + function dot(code) { + if (code === 46) { + effects.consume(code); + return after } - /** - * @param {VFileMessage} message - * @returns {boolean} - */ - function filter(message) { - let gapIndex = gaps.length; + return nok(code) + } + /** @type {State} */ - // Keep messages from a different source. - if (!message.source || !sources.includes(message.source)) { - return true - } + function after(code) { + return code === null || markdownLineEnding(code) ? nok(code) : ok(code) + } +} +/** @type {Tokenizer} */ - // We only ignore messages if they‘re disabled, *not* when they’re not in - // the document. - if (!message.line) { - message.line = 1; - } +function tokenizeDomain(effects, ok, nok) { + /** @type {boolean|undefined} */ + let hasUnderscoreInLastSegment; + /** @type {boolean|undefined} */ - if (!message.column) { - message.column = 1; - } + let hasUnderscoreInLastLastSegment; + return domain + /** @type {State} */ - // Check whether the warning is inside a gap. - // @ts-expect-error: we just normalized `null` to `number`s. - const offset = toOffset(message); + function domain(code) { + if (code === 38) { + return effects.check( + namedCharacterReference, + done, + punctuationContinuation + )(code) + } - while (gapIndex--) { - if (gaps[gapIndex][0] <= offset && gaps[gapIndex][1] > offset) { - return false - } - } + if (code === 46 || code === 95) { + return effects.check(punctuation, done, punctuationContinuation)(code) + } // GH documents that only alphanumerics (other than `-`, `.`, and `_`) can + // occur, which sounds like ASCII only, but they also support `www.點看.com`, + // so that’s Unicode. + // Instead of some new production for Unicode alphanumerics, markdown + // already has that for Unicode punctuation and whitespace, so use those. - // Check whether allowed by specific and global states. - return ( - (!message.ruleId || - check(message, scope[message.ruleId], message.ruleId)) && - check(message, globals) - ) + if ( + code === null || + asciiControl(code) || + unicodeWhitespace(code) || + (code !== 45 && unicodePunctuation(code)) + ) { + return done(code) } - /** - * Helper to check (and possibly warn) if a `ruleId` is unknown. - * - * @param {string} ruleId - * @param {string} verb - * @param {Node} node - * @returns {boolean} - */ - function isKnown(ruleId, verb, node) { - const result = options.known ? options.known.includes(ruleId) : true; - - if (!result) { - file.message( - 'Unknown rule: cannot ' + verb + " `'" + ruleId + "'`", - node - ); - } + effects.consume(code); + return domain + } + /** @type {State} */ - return result + function punctuationContinuation(code) { + if (code === 46) { + hasUnderscoreInLastLastSegment = hasUnderscoreInLastSegment; + hasUnderscoreInLastSegment = undefined; + effects.consume(code); + return domain } - /** - * Get the latest state of a rule. - * When without `ruleId`, gets global state. - * - * @param {string|undefined} ruleId - * @returns {boolean} - */ - function getState(ruleId) { - const ranges = ruleId ? scope[ruleId] : globals; - - if (ranges && ranges.length > 0) { - return ranges[ranges.length - 1].state - } - - if (!ruleId) { - return !reset - } + if (code === 95) hasUnderscoreInLastSegment = true; + effects.consume(code); + return domain + } + /** @type {State} */ - return reset ? enable.includes(ruleId) : !disable.includes(ruleId) + function done(code) { + if (!hasUnderscoreInLastLastSegment && !hasUnderscoreInLastSegment) { + return ok(code) } - /** - * Handle a rule. - * - * @param {Point|undefined} point - * @param {boolean} state - * @param {string|undefined} [ruleId] - * @returns {void} - */ - function toggle(point, state, ruleId) { - let markers = ruleId ? scope[ruleId] : globals; + return nok(code) + } +} +/** @type {Tokenizer} */ - if (!markers) { - markers = []; - scope[String(ruleId)] = markers; - } +function tokenizePath(effects, ok) { + let balance = 0; + return inPath + /** @type {State} */ - const previousState = getState(ruleId); + function inPath(code) { + if (code === 38) { + return effects.check( + namedCharacterReference, + ok, + continuedPunctuation + )(code) + } - if (state !== previousState) { - markers.push({state, point}); - } + if (code === 40) { + balance++; + } - // Toggle all known rules. - if (!ruleId) { - for (ruleId in scope) { - if (own$3.call(scope, ruleId)) { - toggle(point, state, ruleId); - } - } - } + if (code === 41) { + return effects.check( + punctuation, + parenAtPathEnd, + continuedPunctuation + )(code) } - /** - * Check all `ranges` for `message`. - * - * @param {VFileMessage} message - * @param {Mark[]|undefined} ranges - * @param {string|undefined} [ruleId] - * @returns {boolean} - */ - function check(message, ranges, ruleId) { - if (ranges && ranges.length > 0) { - // Check the state at the message’s position. - let index = ranges.length; + if (pathEnd(code)) { + return ok(code) + } - while (index--) { - const range = ranges[index]; + if (trailingPunctuation(code)) { + return effects.check(punctuation, ok, continuedPunctuation)(code) + } - if ( - message.line && - message.column && - range.point && - range.point.line && - range.point.column && - (range.point.line < message.line || - (range.point.line === message.line && - range.point.column <= message.column)) - ) { - return range.state === true - } - } - } + effects.consume(code); + return inPath + } + /** @type {State} */ - // The first marker ocurred after the first message, so we check the - // initial state. - if (!ruleId) { - return Boolean(initial || reset) - } + function continuedPunctuation(code) { + effects.consume(code); + return inPath + } + /** @type {State} */ - return reset ? enable.includes(ruleId) : !disable.includes(ruleId) - } + function parenAtPathEnd(code) { + balance--; + return balance < 0 ? ok(code) : continuedPunctuation(code) } } +/** @type {Tokenizer} */ -/** - * Detect gaps in `tree`. - * - * @param {Node} tree - * @param {VFile} file - */ -function detectGaps(tree, file) { - /** @type {Node[]} */ - // @ts-expect-error: fine. - const children = tree.children || []; - const lastNode = children[children.length - 1]; - /** @type {[number, number][]} */ - const gaps = []; - let offset = 0; - /** @type {boolean|undefined} */ - let gap; +function tokenizeNamedCharacterReference(effects, ok, nok) { + return start + /** @type {State} */ - // Find all gaps. - visit(tree, one); + function start(code) { + effects.consume(code); + return inside + } + /** @type {State} */ - // Get the end of the document. - // This detects if the last node was the last node. - // If not, there’s an extra gap between the last node and the end of the - // document. - if ( - lastNode && - lastNode.position && - lastNode.position.end && - offset === lastNode.position.end.offset && - file.toString().slice(offset).trim() !== '' - ) { - update(); + function inside(code) { + if (asciiAlpha(code)) { + effects.consume(code); + return inside + } - update( - tree && - tree.position && - tree.position.end && - tree.position.end.offset && - tree.position.end.offset - 1 - ); + if (code === 59) { + effects.consume(code); + return after + } + + return nok(code) } + /** @type {State} */ - return gaps + function after(code) { + // If the named character reference is followed by the end of the path, it’s + // not continued punctuation. + return pathEnd(code) ? ok(code) : nok(code) + } +} +/** @type {Tokenizer} */ - /** - * @param {Node} node - */ - function one(node) { - update(node.position && node.position.start && node.position.start.offset); +function tokenizePunctuation(effects, ok, nok) { + return start + /** @type {State} */ - if (!('children' in node)) { - update(node.position && node.position.end && node.position.end.offset); - } + function start(code) { + effects.consume(code); + return after } + /** @type {State} */ - /** - * Detect a new position. - * - * @param {number|undefined} [latest] - * @returns {void} - */ - function update(latest) { - if (latest === null || latest === undefined) { - gap = true; - } else if (offset < latest) { - if (gap) { - gaps.push([offset, latest]); - gap = undefined; - } + function after(code) { + // Check the next. + if (trailingPunctuation(code)) { + effects.consume(code); + return after + } // If the punctuation marker is followed by the end of the path, it’s not + // continued punctuation. - offset = latest; - } + return pathEnd(code) ? ok(code) : nok(code) } } - /** - * @typedef {string|number|boolean} MarkerParameterValue - * @typedef {Object.} MarkerParameters - * - * @typedef HtmlNode - * @property {'html'} type - * @property {string} value - * - * @typedef CommentNode - * @property {'comment'} type - * @property {string} value - * - * @typedef Marker - * @property {string} name - * @property {string} attributes - * @property {MarkerParameters|null} parameters - * @property {HtmlNode|CommentNode} node + * @param {Code} code + * @returns {boolean} */ -var commentExpression = /\s*([a-zA-Z\d-]+)(\s+([\s\S]*))?\s*/; - -var markerExpression = new RegExp( - '(\\s*\\s*)' -); - -/** - * Parse a comment marker. - * @param {unknown} node - * @returns {Marker|null} +function trailingPunctuation(code) { + return ( + code === 33 || + code === 34 || + code === 39 || + code === 41 || + code === 42 || + code === 44 || + code === 46 || + code === 58 || + code === 59 || + code === 60 || + code === 63 || + code === 95 || + code === 126 + ) +} +/** + * @param {Code} code + * @returns {boolean} */ -function commentMarker(node) { - /** @type {RegExpMatchArray} */ - var match; - /** @type {number} */ - var offset; - /** @type {MarkerParameters} */ - var parameters; - if ( - node && - typeof node === 'object' && - // @ts-ignore hush - (node.type === 'html' || node.type === 'comment') - ) { - // @ts-ignore hush - match = node.value.match( - // @ts-ignore hush - node.type === 'comment' ? commentExpression : markerExpression - ); +function pathEnd(code) { + return code === null || code === 60 || markdownLineEndingOrSpace(code) +} +/** + * @param {Code} code + * @returns {boolean} + */ - // @ts-ignore hush - if (match && match[0].length === node.value.length) { - // @ts-ignore hush - offset = node.type === 'comment' ? 1 : 2; - parameters = parseParameters(match[offset + 1] || ''); +function gfmAtext(code) { + return ( + code === 43 || + code === 45 || + code === 46 || + code === 95 || + asciiAlphanumeric(code) + ) +} +/** @type {Previous} */ - if (parameters) { - return { - name: match[offset], - attributes: match[offset + 2] || '', - parameters, - // @ts-ignore hush - node - } - } - } - } +function previousWww(code) { + return ( + code === null || + code === 40 || + code === 42 || + code === 95 || + code === 126 || + markdownLineEndingOrSpace(code) + ) +} +/** @type {Previous} */ - return null +function previousHttp(code) { + return code === null || !asciiAlpha(code) } +/** @type {Previous} */ +function previousEmail(code) { + return code !== 47 && previousHttp(code) +} /** - * Parse `value` into an object. - * - * @param {string} value - * @returns {MarkerParameters|null} + * @param {Event[]} events + * @returns {boolean} */ -function parseParameters(value) { - /** @type {MarkerParameters} */ - var parameters = {}; - - return value - .replace( - /\s+([-\w]+)(?:=(?:"((?:\\[\s\S]|[^"])+)"|'((?:\\[\s\S]|[^'])+)'|((?:\\[\s\S]|[^"'\s])+)))?/gi, - replacer - ) - .replace(/\s+/g, '') - ? null - : parameters - /** - * @param {string} _ - * @param {string} $1 - * @param {string} $2 - * @param {string} $3 - * @param {string} $4 - */ - // eslint-disable-next-line max-params - function replacer(_, $1, $2, $3, $4) { - /** @type {MarkerParameterValue} */ - var value = $2 || $3 || $4 || ''; +function previousUnbalanced(events) { + let index = events.length; + let result = false; - if (value === 'true' || value === '') { - value = true; - } else if (value === 'false') { - value = false; - } else if (!Number.isNaN(Number(value))) { - value = Number(value); - } + while (index--) { + const token = events[index][1]; - parameters[$1] = value; + if ( + (token.type === 'labelLink' || token.type === 'labelImage') && + !token._balanced + ) { + result = true; + break + } // @ts-expect-error If we’ve seen this token, and it was marked as not + // having any unbalanced bracket before it, we can exit. - return '' + if (token._gfmAutolinkLiteralWalkedInto) { + result = false; + break + } } -} - -/** - * @typedef {import('mdast').Root} Root - * @typedef {import('vfile').VFile} VFile - * @typedef {import('unified-message-control')} MessageControl - * @typedef {Omit|Omit} Options - */ -const test = [ - 'html', // Comments are `html` nodes in mdast. - 'comment' // In MDX, comments have their own node. -]; + if (events.length > 0 && !result) { + // @ts-expect-error Mark the last token as “walked into” w/o finding + // anything. + events[events.length - 1][1]._gfmAutolinkLiteralWalkedInto = true; + } -/** - * Plugin to enable, disable, and ignore messages. - * - * @type {import('unified').Plugin<[Options], Root>} - * @returns {(node: Root, file: VFile) => void} - */ -function remarkMessageControl(options) { - return messageControl( - Object.assign({marker: commentMarker, test}, options) - ) + return result } -/** - * @typedef {import('mdast').Root} Root - */ +const characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'}; /** - * The core plugin for `remark-lint`. - * This adds support for ignoring stuff from messages (``). - * All rules are in their own packages and presets. + * Encode only the dangerous HTML characters. * - * @type {import('unified').Plugin} + * This ensures that certain characters which have special meaning in HTML are + * dealt with. + * Technically, we can skip `>` and `"` in many cases, but CM includes them. + * + * @param {string} value + * @returns {string} */ -function remarkLint() { - this.use(lintMessageControl); -} +function encode(value) { + return value.replace(/["&<>]/g, replace) -/** @type {import('unified').Plugin} */ -function lintMessageControl() { - return remarkMessageControl({name: 'lint', source: 'remark-lint'}) + /** + * @param {string} value + * @returns {string} + */ + function replace(value) { + // @ts-expect-error Hush, it’s fine. + return '&' + characterReferences[value] + ';' + } } /** - * @typedef {import('unist').Node} Node - * @typedef {import('vfile').VFile} VFile + * Make a value safe for injection as a URL. * - * @typedef {0|1|2} Severity - * @typedef {'warn'|'on'|'off'|'error'} Label - * @typedef {[Severity, ...unknown[]]} SeverityTuple + * This encodes unsafe characters with percent-encoding and skips already + * encoded sequences (see `normalizeUri` below). + * Further unsafe characters are encoded as character references (see + * `micromark-util-encode`). * - * @typedef RuleMeta - * @property {string} origin name of the lint rule - * @property {string} [url] link to documentation + * Then, a regex of allowed protocols can be given, in which case the URL is + * sanitized. + * For example, `/^(https?|ircs?|mailto|xmpp)$/i` can be used for `a[href]`, + * or `/^https?$/i` for `img[src]`. + * If the URL includes an unknown protocol (one not matched by `protocol`, such + * as a dangerous example, `javascript:`), the value is ignored. * - * @callback Rule - * @param {Node} tree - * @param {VFile} file - * @param {unknown} options - * @returns {void} + * @param {string|undefined} url + * @param {RegExp} [protocol] + * @returns {string} */ +function sanitizeUri(url, protocol) { + const value = encode(normalizeUri(url || '')); -const primitives = new Set(['string', 'number', 'boolean']); + if (!protocol) { + return value + } -/** - * @param {string|RuleMeta} meta - * @param {Rule} rule - */ -function lintRule(meta, rule) { - const id = typeof meta === 'string' ? meta : meta.origin; - const url = typeof meta === 'string' ? undefined : meta.url; - const parts = id.split(':'); - // Possibly useful if externalised later. - /* c8 ignore next */ - const source = parts[1] ? parts[0] : undefined; - const ruleId = parts[1]; + const colon = value.indexOf(':'); + const questionMark = value.indexOf('?'); + const numberSign = value.indexOf('#'); + const slash = value.indexOf('/'); - Object.defineProperty(plugin, 'name', {value: id}); + if ( + // If there is no protocol, it’s relative. + colon < 0 || // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol. + (slash > -1 && colon > slash) || + (questionMark > -1 && colon > questionMark) || + (numberSign > -1 && colon > numberSign) || // It is a protocol, it should be allowed. + protocol.test(value.slice(0, colon)) + ) { + return value + } - return plugin + return '' +} +/** + * Normalize a URL (such as used in definitions). + * + * Encode unsafe characters with percent-encoding, skipping already encoded + * sequences. + * + * @param {string} value + * @returns {string} + */ - /** @type {import('unified').Plugin<[unknown]|void[]>} */ - function plugin(raw) { - const [severity, options] = coerce$1(ruleId, raw); - - if (!severity) return +function normalizeUri(value) { + /** @type {string[]} */ + const result = []; + let index = -1; + let start = 0; + let skip = 0; - const fatal = severity === 2; + while (++index < value.length) { + const code = value.charCodeAt(index); + /** @type {string} */ - return (tree, file, next) => { - let index = file.messages.length - 1; + let replace = ''; // A correct percent encoded value. - wrap(rule, (error) => { - const messages = file.messages; + if ( + code === 37 && + asciiAlphanumeric(value.charCodeAt(index + 1)) && + asciiAlphanumeric(value.charCodeAt(index + 2)) + ) { + skip = 2; + } // ASCII. + else if (code < 128) { + if (!/[!#$&-;=?-Z_a-z~]/.test(String.fromCharCode(code))) { + replace = String.fromCharCode(code); + } + } // Astral. + else if (code > 55295 && code < 57344) { + const next = value.charCodeAt(index + 1); // A correct surrogate pair. - // Add the error, if not already properly added. - // Only happens for incorrect plugins. - /* c8 ignore next 6 */ - // @ts-expect-error: errors could be `messages`. - if (error && !messages.includes(error)) { - try { - file.fail(error); - } catch {} - } + if (code < 56320 && next > 56319 && next < 57344) { + replace = String.fromCharCode(code, next); + skip = 1; + } // Lone surrogate. + else { + replace = '\uFFFD'; + } + } // Unicode. + else { + replace = String.fromCharCode(code); + } - while (++index < messages.length) { - Object.assign(messages[index], {ruleId, source, fatal, url}); - } + if (replace) { + result.push(value.slice(start, index), encodeURIComponent(replace)); + start = index + skip + 1; + replace = ''; + } - next(); - })(tree, file, options); + if (skip) { + index += skip; + skip = 0; } } + + return result.join('') + value.slice(start) } /** - * Coerce a value to a severity--options tuple. - * - * @param {string} name - * @param {unknown} value - * @returns {SeverityTuple} + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension + * @typedef {import('micromark-util-types').Handle} Handle + * @typedef {import('micromark-util-types').CompileContext} CompileContext + * @typedef {import('micromark-util-types').Token} Token */ -function coerce$1(name, value) { - /** @type {unknown[]} */ - let result; +/** @type {HtmlExtension} */ - if (typeof value === 'boolean') { - result = [value]; - } else if (value === null || value === undefined) { - result = [1]; - } else if ( - Array.isArray(value) && - // `isArray(unknown)` is turned into `any[]`: - // type-coverage:ignore-next-line - primitives.has(typeof value[0]) - ) { - // `isArray(unknown)` is turned into `any[]`: - // type-coverage:ignore-next-line - result = [...value]; - } else { - result = [1, value]; +const gfmAutolinkLiteralHtml = { + exit: { + literalAutolinkEmail, + literalAutolinkHttp, + literalAutolinkWww } +}; +/** @type {Handle} */ - let level = result[0]; - - if (typeof level === 'boolean') { - level = level ? 1 : 0; - } else if (typeof level === 'string') { - if (level === 'off') { - level = 0; - } else if (level === 'on' || level === 'warn') { - level = 1; - } else if (level === 'error') { - level = 2; - } else { - level = 1; - result = [level, result]; - } - } +function literalAutolinkWww(token) { + anchorFromToken.call(this, token, 'http://'); +} +/** @type {Handle} */ - if (typeof level !== 'number' || level < 0 || level > 2) { - throw new Error( - 'Incorrect severity `' + - level + - '` for `' + - name + - '`, ' + - 'expected 0, 1, or 2' - ) - } +function literalAutolinkEmail(token) { + anchorFromToken.call(this, token, 'mailto:'); +} +/** @type {Handle} */ - result[0] = level; +function literalAutolinkHttp(token) { + anchorFromToken.call(this, token); +} +/** + * @this CompileContext + * @param {Token} token + * @param {string} [protocol] + * @returns {void} + */ - // @ts-expect-error: it’s now a valid tuple. - return result +function anchorFromToken(token, protocol) { + const url = this.sliceSerialize(token); + this.tag(''); + this.raw(this.encode(url)); + this.tag(''); } /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module final-newline - * @fileoverview - * Warn when a line feed at the end of a file is missing. - * Empty files are allowed. - * - * See [StackExchange](https://unix.stackexchange.com/questions/18743) for why. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * always adds a final line feed to files. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * ## Example - * - * ##### `ok.md` - * - * ###### In - * - * Note: `␊` represents LF. - * - * ```markdown - * Alpha␊ - * ``` - * - * ###### Out - * - * No messages. - * - * ##### `not-ok.md` - * - * ###### In - * - * Note: The below file does not have a final newline. - * - * ```markdown - * Bravo - * ``` - * - * ###### Out - * - * ```text - * 1:1: Missing newline character at end of file - * ``` + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension */ -const remarkLintFinalNewline = lintRule( - { - origin: 'remark-lint:final-newline', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme' +/** @type {HtmlExtension} */ +const gfmStrikethroughHtml = { + enter: { + strikethrough() { + this.tag(''); + } }, - /** @type {import('unified-lint-rule').Rule} */ - (_, file) => { - const value = String(file); - const last = value.length - 1; - - if (last > -1 && value.charAt(last) !== '\n') { - file.message('Missing newline character at end of file'); + exit: { + strikethrough() { + this.tag(''); } } -); +}; -var remarkLintFinalNewline$1 = remarkLintFinalNewline; +/** + * @typedef {import('micromark-util-types').Extension} Extension + * @typedef {import('micromark-util-types').Resolver} Resolver + * @typedef {import('micromark-util-types').Tokenizer} Tokenizer + * @typedef {import('micromark-util-types').State} State + * @typedef {import('micromark-util-types').Token} Token + * @typedef {import('micromark-util-types').Event} Event + */ -var pluralize = {exports: {}}; +/** + * @param {Options} [options] + * @returns {Extension} + */ +function gfmStrikethrough(options = {}) { + let single = options.singleTilde; + const tokenizer = { + tokenize: tokenizeStrikethrough, + resolveAll: resolveAllStrikethrough + }; -/* global define */ + if (single === null || single === undefined) { + single = true; + } -(function (module, exports) { -(function (root, pluralize) { - /* istanbul ignore else */ - if (typeof commonjsRequire === 'function' && 'object' === 'object' && 'object' === 'object') { - // Node. - module.exports = pluralize(); - } else { - // Browser global. - root.pluralize = pluralize(); - } -})(commonjsGlobal, function () { - // Rule storage - pluralize and singularize need to be run sequentially, - // while other rules can be optimized using an object for instant lookups. - var pluralRules = []; - var singularRules = []; - var uncountables = {}; - var irregularPlurals = {}; - var irregularSingles = {}; - - /** - * Sanitize a pluralization rule to a usable regular expression. - * - * @param {(RegExp|string)} rule - * @return {RegExp} - */ - function sanitizeRule (rule) { - if (typeof rule === 'string') { - return new RegExp('^' + rule + '$', 'i'); + return { + text: { + [126]: tokenizer + }, + insideSpan: { + null: [tokenizer] + }, + attentionMarkers: { + null: [126] } - - return rule; } - /** - * Pass in a word token to produce a function that can replicate the case on - * another word. + * Take events and resolve strikethrough. * - * @param {string} word - * @param {string} token - * @return {Function} + * @type {Resolver} */ - function restoreCase (word, token) { - // Tokens are an exact match. - if (word === token) return token; - // Lower cased words. E.g. "hello". - if (word === word.toLowerCase()) return token.toLowerCase(); + function resolveAllStrikethrough(events, context) { + let index = -1; + /** @type {Token} */ - // Upper cased words. E.g. "WHISKY". - if (word === word.toUpperCase()) return token.toUpperCase(); + let strikethrough; + /** @type {Token} */ - // Title cased words. E.g. "Title". - if (word[0] === word[0].toUpperCase()) { - return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase(); - } + let text; + /** @type {number} */ - // Lower cased words. E.g. "test". - return token.toLowerCase(); - } + let open; + /** @type {Event[]} */ - /** - * Interpolate a regexp string. - * - * @param {string} str - * @param {Array} args - * @return {string} - */ - function interpolate (str, args) { - return str.replace(/\$(\d{1,2})/g, function (match, index) { - return args[index] || ''; - }); - } + let nextEvents; // Walk through all events. - /** - * Replace a word using a rule. - * - * @param {string} word - * @param {Array} rule - * @return {string} - */ - function replace (word, rule) { - return word.replace(rule[0], function (match, index) { - var result = interpolate(rule[1], arguments); + while (++index < events.length) { + // Find a token that can close. + if ( + events[index][0] === 'enter' && + events[index][1].type === 'strikethroughSequenceTemporary' && + events[index][1]._close + ) { + open = index; // Now walk back to find an opener. - if (match === '') { - return restoreCase(word[index - 1], result); - } + while (open--) { + // Find a token that can open the closer. + if ( + events[open][0] === 'exit' && + events[open][1].type === 'strikethroughSequenceTemporary' && + events[open][1]._open && // If the sizes are the same: + events[index][1].end.offset - events[index][1].start.offset === + events[open][1].end.offset - events[open][1].start.offset + ) { + events[index][1].type = 'strikethroughSequence'; + events[open][1].type = 'strikethroughSequence'; + strikethrough = { + type: 'strikethrough', + start: Object.assign({}, events[open][1].start), + end: Object.assign({}, events[index][1].end) + }; + text = { + type: 'strikethroughText', + start: Object.assign({}, events[open][1].end), + end: Object.assign({}, events[index][1].start) + }; // Opening. - return restoreCase(match, result); - }); - } + nextEvents = [ + ['enter', strikethrough, context], + ['enter', events[open][1], context], + ['exit', events[open][1], context], + ['enter', text, context] + ]; // Between. - /** - * Sanitize a word by passing in the word and sanitization rules. - * - * @param {string} token - * @param {string} word - * @param {Array} rules - * @return {string} - */ - function sanitizeWord (token, word, rules) { - // Empty string or doesn't need fixing. - if (!token.length || uncountables.hasOwnProperty(token)) { - return word; - } + splice( + nextEvents, + nextEvents.length, + 0, + resolveAll( + context.parser.constructs.insideSpan.null, + events.slice(open + 1, index), + context + ) + ); // Closing. - var len = rules.length; + splice(nextEvents, nextEvents.length, 0, [ + ['exit', text, context], + ['enter', events[index][1], context], + ['exit', events[index][1], context], + ['exit', strikethrough, context] + ]); + splice(events, open - 1, index - open + 3, nextEvents); + index = open + nextEvents.length - 2; + break + } + } + } + } - // Iterate over the sanitization rules and use the first one to match. - while (len--) { - var rule = rules[len]; + index = -1; - if (rule[0].test(word)) return replace(word, rule); + while (++index < events.length) { + if (events[index][1].type === 'strikethroughSequenceTemporary') { + events[index][1].type = 'data'; + } } - return word; + return events } + /** @type {Tokenizer} */ - /** - * Replace a word with the updated word. - * - * @param {Object} replaceMap - * @param {Object} keepMap - * @param {Array} rules - * @return {Function} - */ - function replaceWord (replaceMap, keepMap, rules) { - return function (word) { - // Get the correct token and case restoration functions. - var token = word.toLowerCase(); + function tokenizeStrikethrough(effects, ok, nok) { + const previous = this.previous; + const events = this.events; + let size = 0; + return start + /** @type {State} */ - // Check against the keep object map. - if (keepMap.hasOwnProperty(token)) { - return restoreCase(word, token); + function start(code) { + if ( + code !== 126 || + (previous === 126 && + events[events.length - 1][1].type !== 'characterEscape') + ) { + return nok(code) } - // Check against the replacement map for a direct word replacement. - if (replaceMap.hasOwnProperty(token)) { - return restoreCase(word, replaceMap[token]); + effects.enter('strikethroughSequenceTemporary'); + return more(code) + } + /** @type {State} */ + + function more(code) { + const before = classifyCharacter(previous); + + if (code === 126) { + // If this is the third marker, exit. + if (size > 1) return nok(code) + effects.consume(code); + size++; + return more } - // Run all the rules against the word. - return sanitizeWord(token, word, rules); - }; + if (size < 2 && !single) return nok(code) + const token = effects.exit('strikethroughSequenceTemporary'); + const after = classifyCharacter(code); + token._open = !after || (after === 2 && Boolean(before)); + token._close = !before || (before === 2 && Boolean(after)); + return ok(code) + } } +} - /** - * Check if a word is part of the map. - */ - function checkWord (replaceMap, keepMap, rules, bool) { - return function (word) { - var token = word.toLowerCase(); +/** + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension + */ - if (keepMap.hasOwnProperty(token)) return true; - if (replaceMap.hasOwnProperty(token)) return false; +/** + * @typedef {import('./syntax.js').Align} Align + */ +const alignment = { + null: '', + left: ' align="left"', + right: ' align="right"', + center: ' align="center"' +}; +/** @type {HtmlExtension} */ - return sanitizeWord(token, token, rules) === token; - }; - } +const gfmTableHtml = { + enter: { + table(token) { + this.lineEndingIfNeeded(); + this.tag(''); // @ts-expect-error Custom. - /** - * Pluralize or singularize a word based on the passed in count. - * - * @param {string} word The word to pluralize - * @param {number} count How many of the word exist - * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks) - * @return {string} - */ - function pluralize (word, count, inclusive) { - var pluralized = count === 1 - ? pluralize.singular(word) : pluralize.plural(word); + this.setData('tableAlign', token._align); + }, - return (inclusive ? count + ' ' : '') + pluralized; - } + tableBody() { + // Clear slurping line ending from the delimiter row. + this.setData('slurpOneLineEnding'); + this.tag(''); + }, - /** - * Pluralize a word. - * - * @type {Function} - */ - pluralize.plural = replaceWord( - irregularSingles, irregularPlurals, pluralRules - ); + tableData() { + /** @type {string|undefined} */ + const align = // @ts-expect-error Custom. + alignment[this.getData('tableAlign')[this.getData('tableColumn')]]; - /** - * Check if a word is plural. - * - * @type {Function} - */ - pluralize.isPlural = checkWord( - irregularSingles, irregularPlurals, pluralRules - ); + if (align === undefined) { + // Capture results to ignore them. + this.buffer(); + } else { + this.lineEndingIfNeeded(); + this.tag(''); + } + }, - /** - * Singularize a word. - * - * @type {Function} - */ - pluralize.singular = replaceWord( - irregularPlurals, irregularSingles, singularRules - ); + tableHead() { + this.lineEndingIfNeeded(); + this.tag(''); + }, - /** - * Check if a word is singular. - * - * @type {Function} - */ - pluralize.isSingular = checkWord( - irregularPlurals, irregularSingles, singularRules - ); + tableHeader() { + this.lineEndingIfNeeded(); + this.tag( + '' + ); + }, - /** - * Add a pluralization rule to the collection. - * - * @param {(string|RegExp)} rule - * @param {string} replacement - */ - pluralize.addPluralRule = function (rule, replacement) { - pluralRules.push([sanitizeRule(rule), replacement]); - }; + tableRow() { + this.setData('tableColumn', 0); + this.lineEndingIfNeeded(); + this.tag(''); + } + }, + exit: { + // Overwrite the default code text data handler to unescape escaped pipes when + // they are in tables. + codeTextData(token) { + let value = this.sliceSerialize(token); - /** - * Add a singularization rule to the collection. - * - * @param {(string|RegExp)} rule - * @param {string} replacement - */ - pluralize.addSingularRule = function (rule, replacement) { - singularRules.push([sanitizeRule(rule), replacement]); - }; + if (this.getData('tableAlign')) { + value = value.replace(/\\([\\|])/g, replace$1); + } - /** - * Add an uncountable word rule. - * - * @param {(string|RegExp)} word - */ - pluralize.addUncountableRule = function (word) { - if (typeof word === 'string') { - uncountables[word.toLowerCase()] = true; - return; - } + this.raw(this.encode(value)); + }, - // Set singular and plural references for the word. - pluralize.addPluralRule(word, '$0'); - pluralize.addSingularRule(word, '$0'); - }; + table() { + this.setData('tableAlign'); // If there was no table body, make sure the slurping from the delimiter row + // is cleared. - /** - * Add an irregular word definition. - * - * @param {string} single - * @param {string} plural - */ - pluralize.addIrregularRule = function (single, plural) { - plural = plural.toLowerCase(); - single = single.toLowerCase(); + this.setData('slurpAllLineEndings'); + this.lineEndingIfNeeded(); + this.tag('
'); + }, - irregularSingles[single] = plural; - irregularPlurals[plural] = single; - }; + tableBody() { + this.lineEndingIfNeeded(); + this.tag(''); + }, - /** - * Irregular rules. - */ - [ - // Pronouns. - ['I', 'we'], - ['me', 'us'], - ['he', 'they'], - ['she', 'they'], - ['them', 'them'], - ['myself', 'ourselves'], - ['yourself', 'yourselves'], - ['itself', 'themselves'], - ['herself', 'themselves'], - ['himself', 'themselves'], - ['themself', 'themselves'], - ['is', 'are'], - ['was', 'were'], - ['has', 'have'], - ['this', 'these'], - ['that', 'those'], - // Words ending in with a consonant and `o`. - ['echo', 'echoes'], - ['dingo', 'dingoes'], - ['volcano', 'volcanoes'], - ['tornado', 'tornadoes'], - ['torpedo', 'torpedoes'], - // Ends with `us`. - ['genus', 'genera'], - ['viscus', 'viscera'], - // Ends with `ma`. - ['stigma', 'stigmata'], - ['stoma', 'stomata'], - ['dogma', 'dogmata'], - ['lemma', 'lemmata'], - ['schema', 'schemata'], - ['anathema', 'anathemata'], - // Other irregular rules. - ['ox', 'oxen'], - ['axe', 'axes'], - ['die', 'dice'], - ['yes', 'yeses'], - ['foot', 'feet'], - ['eave', 'eaves'], - ['goose', 'geese'], - ['tooth', 'teeth'], - ['quiz', 'quizzes'], - ['human', 'humans'], - ['proof', 'proofs'], - ['carve', 'carves'], - ['valve', 'valves'], - ['looey', 'looies'], - ['thief', 'thieves'], - ['groove', 'grooves'], - ['pickaxe', 'pickaxes'], - ['passerby', 'passersby'] - ].forEach(function (rule) { - return pluralize.addIrregularRule(rule[0], rule[1]); - }); + tableData() { + /** @type {number} */ + // @ts-expect-error Custom. + const column = this.getData('tableColumn'); // @ts-expect-error Custom. - /** - * Pluralization rules. - */ - [ - [/s?$/i, 's'], - [/[^\u0000-\u007F]$/i, '$0'], - [/([^aeiou]ese)$/i, '$1'], - [/(ax|test)is$/i, '$1es'], - [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'], - [/(e[mn]u)s?$/i, '$1s'], - [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'], - [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'], - [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'], - [/(seraph|cherub)(?:im)?$/i, '$1im'], - [/(her|at|gr)o$/i, '$1oes'], - [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'], - [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'], - [/sis$/i, 'ses'], - [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'], - [/([^aeiouy]|qu)y$/i, '$1ies'], - [/([^ch][ieo][ln])ey$/i, '$1ies'], - [/(x|ch|ss|sh|zz)$/i, '$1es'], - [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'], - [/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'], - [/(pe)(?:rson|ople)$/i, '$1ople'], - [/(child)(?:ren)?$/i, '$1ren'], - [/eaux$/i, '$0'], - [/m[ae]n$/i, 'men'], - ['thou', 'you'] - ].forEach(function (rule) { - return pluralize.addPluralRule(rule[0], rule[1]); - }); + if (column in this.getData('tableAlign')) { + this.tag(''); + this.setData('tableColumn', column + 1); + } else { + // Stop capturing. + this.resume(); + } + }, - /** - * Singularization rules. - */ - [ - [/s$/i, ''], - [/(ss)$/i, '$1'], - [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'], - [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'], - [/ies$/i, 'y'], - [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'], - [/\b(mon|smil)ies$/i, '$1ey'], - [/\b((?:tit)?m|l)ice$/i, '$1ouse'], - [/(seraph|cherub)im$/i, '$1'], - [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'], - [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'], - [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'], - [/(test)(?:is|es)$/i, '$1is'], - [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'], - [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'], - [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'], - [/(alumn|alg|vertebr)ae$/i, '$1a'], - [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'], - [/(matr|append)ices$/i, '$1ix'], - [/(pe)(rson|ople)$/i, '$1rson'], - [/(child)ren$/i, '$1'], - [/(eau)x?$/i, '$1'], - [/men$/i, 'man'] - ].forEach(function (rule) { - return pluralize.addSingularRule(rule[0], rule[1]); - }); - - /** - * Uncountable rules. - */ - [ - // Singular words with no plurals. - 'adulthood', - 'advice', - 'agenda', - 'aid', - 'aircraft', - 'alcohol', - 'ammo', - 'analytics', - 'anime', - 'athletics', - 'audio', - 'bison', - 'blood', - 'bream', - 'buffalo', - 'butter', - 'carp', - 'cash', - 'chassis', - 'chess', - 'clothing', - 'cod', - 'commerce', - 'cooperation', - 'corps', - 'debris', - 'diabetes', - 'digestion', - 'elk', - 'energy', - 'equipment', - 'excretion', - 'expertise', - 'firmware', - 'flounder', - 'fun', - 'gallows', - 'garbage', - 'graffiti', - 'hardware', - 'headquarters', - 'health', - 'herpes', - 'highjinks', - 'homework', - 'housework', - 'information', - 'jeans', - 'justice', - 'kudos', - 'labour', - 'literature', - 'machinery', - 'mackerel', - 'mail', - 'media', - 'mews', - 'moose', - 'music', - 'mud', - 'manga', - 'news', - 'only', - 'personnel', - 'pike', - 'plankton', - 'pliers', - 'police', - 'pollution', - 'premises', - 'rain', - 'research', - 'rice', - 'salmon', - 'scissors', - 'series', - 'sewage', - 'shambles', - 'shrimp', - 'software', - 'species', - 'staff', - 'swine', - 'tennis', - 'traffic', - 'transportation', - 'trout', - 'tuna', - 'wealth', - 'welfare', - 'whiting', - 'wildebeest', - 'wildlife', - 'you', - /pok[eé]mon$/i, - // Regexes. - /[^aeiou]ese$/i, // "chinese", "japanese" - /deer$/i, // "deer", "reindeer" - /fish$/i, // "fish", "blowfish", "angelfish" - /measles$/i, - /o[iu]s$/i, // "carnivorous" - /pox$/i, // "chickpox", "smallpox" - /sheep$/i - ].forEach(pluralize.addUncountableRule); - - return pluralize; -}); -}(pluralize)); + tableHead() { + this.lineEndingIfNeeded(); + this.tag(''); + this.setData('slurpOneLineEnding', true); // Slurp the line ending from the delimiter row. + }, -var plural = pluralize.exports; + tableHeader() { + this.tag(''); // @ts-expect-error Custom. -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module list-item-bullet-indent - * @fileoverview - * Warn when list item bullets are indented. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * removes all indentation before bullets. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * Paragraph. - * - * * List item - * * List item - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * Paragraph. - * - * ·* List item - * ·* List item - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 3:2: Incorrect indentation before bullet: remove 1 space - * 4:2: Incorrect indentation before bullet: remove 1 space - */ + this.setData('tableColumn', this.getData('tableColumn') + 1); + }, -const remarkLintListItemBulletIndent = lintRule( - { - origin: 'remark-lint:list-item-bullet-indent', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-list-item-bullet-indent#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'list', (list, _, grandparent) => { - let index = -1; + tableRow() { + /** @type {Align[]} */ + // @ts-expect-error Custom. + const align = this.getData('tableAlign'); + /** @type {number} */ + // @ts-expect-error Custom. - while (++index < list.children.length) { - const item = list.children[index]; + let column = this.getData('tableColumn'); - if ( - grandparent && - grandparent.type === 'root' && - grandparent.position && - typeof grandparent.position.start.column === 'number' && - item.position && - typeof item.position.start.column === 'number' - ) { - const indent = - item.position.start.column - grandparent.position.start.column; + while (column < align.length) { + this.lineEndingIfNeeded(); // @ts-expect-error `null` is fine as an index. - if (indent) { - file.message( - 'Incorrect indentation before bullet: remove ' + - indent + - ' ' + - plural('space', indent), - item.position.start - ); - } - } + this.tag(''); + column++; } - }); + + this.setData('tableColumn', column); + this.lineEndingIfNeeded(); + this.tag(''); + } } -); +}; +/** + * @param {string} $0 + * @param {string} $1 + * @returns {string} + */ -var remarkLintListItemBulletIndent$1 = remarkLintListItemBulletIndent; +function replace$1($0, $1) { + // Pipes work, backslashes don’t (but can’t escape pipes). + return $1 === '|' ? $1 : $0 +} /** - * @typedef {import('unist').Position} Position - * @typedef {import('unist').Point} Point - * - * @typedef {Partial} PointLike - * - * @typedef {Object} PositionLike - * @property {PointLike} [start] - * @property {PointLike} [end] - * - * @typedef {Object} NodeLike - * @property {PositionLike} [position] - */ - -var pointStart = point('start'); -var pointEnd = point('end'); - -/** - * Get the positional info of `node`. - * - * @param {'start'|'end'} type + * @typedef {import('micromark-util-types').Extension} Extension + * @typedef {import('micromark-util-types').Resolver} Resolver + * @typedef {import('micromark-util-types').Tokenizer} Tokenizer + * @typedef {import('micromark-util-types').State} State + * @typedef {import('micromark-util-types').Token} Token */ -function point(type) { - return point - - /** - * Get the positional info of `node`. - * - * @param {NodeLike} [node] - * @returns {Point} - */ - function point(node) { - /** @type {Point} */ - // @ts-ignore looks like a point - var point = (node && node.position && node.position[type]) || {}; - return { - line: point.line || null, - column: point.column || null, - offset: point.offset > -1 ? point.offset : null +/** @type {Extension} */ +const gfmTable = { + flow: { + null: { + tokenize: tokenizeTable, + resolve: resolveTable } } -} - -/** - * @typedef {Object} PointLike - * @property {number} [line] - * @property {number} [column] - * @property {number} [offset] - * - * @typedef {Object} PositionLike - * @property {PointLike} [start] - * @property {PointLike} [end] - * - * @typedef {Object} NodeLike - * @property {PositionLike} [position] - */ +}; +const setextUnderlineMini = { + tokenize: tokenizeSetextUnderlineMini, + partial: true +}; +const nextPrefixedOrBlank = { + tokenize: tokenizeNextPrefixedOrBlank, + partial: true +}; +/** @type {Resolver} */ -/** - * Check if `node` is *generated*. - * - * @param {NodeLike} [node] - * @returns {boolean} - */ -function generated(node) { - return ( - !node || - !node.position || - !node.position.start || - !node.position.start.line || - !node.position.start.column || - !node.position.end || - !node.position.end.line || - !node.position.end.column - ) -} +function resolveTable(events, context) { + let index = -1; + /** @type {Token} */ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module list-item-indent - * @fileoverview - * Warn when the spacing between a list item’s bullet and its content violates - * a given style. - * - * Options: `'tab-size'`, `'mixed'`, or `'space'`, default: `'tab-size'`. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * uses `'tab-size'` (named `'tab'` there) by default to ensure Markdown is - * seen the same way across vendors. - * This can be configured with the - * [`listItemIndent`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionslistitemindent) - * option. - * This rule’s `'space'` option is named `'1'` there. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * *···List - * ····item. - * - * Paragraph. - * - * 11.·List - * ····item. - * - * Paragraph. - * - * *···List - * ····item. - * - * *···List - * ····item. - * - * @example - * {"name": "ok.md", "setting": "mixed"} - * - * *·List item. - * - * Paragraph. - * - * 11.·List item - * - * Paragraph. - * - * *···List - * ····item. - * - * *···List - * ····item. - * - * @example - * {"name": "ok.md", "setting": "space"} - * - * *·List item. - * - * Paragraph. - * - * 11.·List item - * - * Paragraph. - * - * *·List - * ··item. - * - * *·List - * ··item. - * - * @example - * {"name": "not-ok.md", "setting": "space", "label": "input"} - * - * *···List - * ····item. - * - * @example - * {"name": "not-ok.md", "setting": "space", "label": "output"} - * - * 1:5: Incorrect list-item indent: remove 2 spaces - * - * @example - * {"name": "not-ok.md", "setting": "tab-size", "label": "input"} - * - * *·List - * ··item. - * - * @example - * {"name": "not-ok.md", "setting": "tab-size", "label": "output"} - * - * 1:3: Incorrect list-item indent: add 2 spaces - * - * @example - * {"name": "not-ok.md", "setting": "mixed", "label": "input"} - * - * *···List item. - * - * @example - * {"name": "not-ok.md", "setting": "mixed", "label": "output"} - * - * 1:5: Incorrect list-item indent: remove 2 spaces - * - * @example - * {"name": "not-ok.md", "setting": "💩", "label": "output", "positionless": true} - * - * 1:1: Incorrect list-item indent style `💩`: use either `'tab-size'`, `'space'`, or `'mixed'` - */ + let token; + /** @type {boolean|undefined} */ -const remarkLintListItemIndent = lintRule( - { - origin: 'remark-lint:list-item-indent', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-list-item-indent#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'tab-size') => { - const value = String(file); + let inHead; + /** @type {boolean|undefined} */ - if (option !== 'tab-size' && option !== 'space' && option !== 'mixed') { - file.fail( - 'Incorrect list-item indent style `' + - option + - "`: use either `'tab-size'`, `'space'`, or `'mixed'`" - ); - } + let inDelimiterRow; + /** @type {boolean|undefined} */ - visit$1(tree, 'list', (node) => { - if (generated(node)) return + let inRow; + /** @type {Token} */ - const spread = node.spread; - let index = -1; + let cell; + /** @type {Token} */ - while (++index < node.children.length) { - const item = node.children[index]; - const head = item.children[0]; - const final = pointStart(head); + let content; + /** @type {Token} */ - const marker = value - .slice(pointStart(item).offset, final.offset) - .replace(/\[[x ]?]\s*$/i, ''); + let text; + /** @type {number|undefined} */ - const bulletSize = marker.replace(/\s+$/, '').length; + let contentStart; + /** @type {number|undefined} */ - const style = - option === 'tab-size' || (option === 'mixed' && spread) - ? Math.ceil(bulletSize / 4) * 4 - : bulletSize + 1; + let contentEnd; + /** @type {number|undefined} */ - if (marker.length !== style) { - const diff = style - marker.length; - const abs = Math.abs(diff); + let cellStart; - file.message( - 'Incorrect list-item indent: ' + - (diff > 0 ? 'add' : 'remove') + - ' ' + - abs + - ' ' + - plural('space', abs), - final - ); - } - } - }); - } -); - -var remarkLintListItemIndent$1 = remarkLintListItemIndent; - -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-auto-link-without-protocol - * @fileoverview - * Warn for autolinks without protocol. - * Autolinks are URLs enclosed in `<` (less than) and `>` (greater than) - * characters. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * adds a protocol where needed. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * - * - * - * Most Markdown vendors don’t recognize the following as a link: - * - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-1:14: All automatic links must start with a protocol - */ + while (++index < events.length) { + token = events[index][1]; -// Protocol expression. -// See: . -const protocol = /^[a-z][a-z+.-]+:\/?/i; + if (inRow) { + if (token.type === 'temporaryTableCellContent') { + contentStart = contentStart || index; + contentEnd = index; + } -const remarkLintNoAutoLinkWithoutProtocol = lintRule( - { - origin: 'remark-lint:no-auto-link-without-protocol', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-auto-link-without-protocol#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'link', (node) => { if ( - !generated(node) && - pointStart(node).column === pointStart(node.children[0]).column - 1 && - pointEnd(node).column === - pointEnd(node.children[node.children.length - 1]).column + 1 && - !protocol.test(toString(node)) + // Combine separate content parts into one. + (token.type === 'tableCellDivider' || token.type === 'tableRow') && + contentEnd ) { - file.message('All automatic links must start with a protocol', node); - } - }); - } -); + content = { + type: 'tableContent', + // @ts-expect-error `contentStart` is defined if `contentEnd` is too. + start: events[contentStart][1].start, + end: events[contentEnd][1].end + }; + text = { + type: 'chunkText', + start: content.start, + end: content.end, + // @ts-expect-error It’s fine. + contentType: 'text' + }; + events.splice( + // @ts-expect-error `contentStart` is defined if `contentEnd` is too. + contentStart, // @ts-expect-error `contentStart` is defined if `contentEnd` is too. + contentEnd - contentStart + 1, + ['enter', content, context], + ['enter', text, context], + ['exit', text, context], + ['exit', content, context] + ); // @ts-expect-error `contentStart` is defined if `contentEnd` is too. -var remarkLintNoAutoLinkWithoutProtocol$1 = remarkLintNoAutoLinkWithoutProtocol; + index -= contentEnd - contentStart - 3; + contentStart = undefined; + contentEnd = undefined; + } + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-blockquote-without-marker - * @fileoverview - * Warn when blank lines without `>` (greater than) markers are found in a - * block quote. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * adds markers to every line in a block quote. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * > Foo… - * > …bar… - * > …baz. - * - * @example - * {"name": "ok-tabs.md"} - * - * >»Foo… - * >»…bar… - * >»…baz. - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * > Foo… - * …bar… - * > …baz. - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 2:1: Missing marker in block quote - * - * @example - * {"name": "not-ok-tabs.md", "label": "input"} - * - * >»Foo… - * »…bar… - * …baz. - * - * @example - * {"name": "not-ok-tabs.md", "label": "output"} - * - * 2:1: Missing marker in block quote - * 3:1: Missing marker in block quote - */ + if ( + events[index][0] === 'exit' && + cellStart && + cellStart + 1 < index && + (token.type === 'tableCellDivider' || + (token.type === 'tableRow' && + (cellStart + 3 < index || + events[cellStart][1].type !== 'whitespace'))) + ) { + cell = { + type: inDelimiterRow + ? 'tableDelimiter' + : inHead + ? 'tableHeader' + : 'tableData', + start: events[cellStart][1].start, + end: events[index][1].end + }; + events.splice(index + (token.type === 'tableCellDivider' ? 1 : 0), 0, [ + 'exit', + cell, + context + ]); + events.splice(cellStart, 0, ['enter', cell, context]); + index += 2; + cellStart = index + 1; + } -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' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - const value = String(file); - const loc = location(file); + if (token.type === 'tableRow') { + inRow = events[index][0] === 'enter'; - visit$1(tree, 'blockquote', (node) => { - let index = -1; + if (inRow) { + cellStart = index + 1; + } + } - while (++index < node.children.length) { - const child = node.children[index]; + if (token.type === 'tableDelimiterRow') { + inDelimiterRow = events[index][0] === 'enter'; - if (child.type === 'paragraph' && !generated(child)) { - const end = pointEnd(child).line; - const column = pointStart(child).column; - let line = pointStart(child).line; + if (inDelimiterRow) { + cellStart = index + 1; + } + } - // Skip past the first line. - while (++line <= end) { - const offset = loc.toOffset({line, column}); + if (token.type === 'tableHead') { + inHead = events[index][0] === 'enter'; + } + } - if (/>[\t ]+$/.test(value.slice(offset - 5, offset))) { - continue - } + return events +} +/** @type {Tokenizer} */ - // Roughly here. - file.message('Missing marker in block quote', { - line, - column: column - 2 - }); - } - } - } - }); - } -); +function tokenizeTable(effects, ok, nok) { + const self = this; + /** @type {Align[]} */ -var remarkLintNoBlockquoteWithoutMarker$1 = remarkLintNoBlockquoteWithoutMarker; + const align = []; + let tableHeaderCount = 0; + /** @type {boolean|undefined} */ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-literal-urls - * @fileoverview - * Warn for literal URLs in text. - * URLs are treated as links in some Markdown vendors, but not in others. - * To make sure they are always linked, wrap them in `<` (less than) and `>` - * (greater than). - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * never creates literal URLs and always uses `<` (less than) and `>` - * (greater than). - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * - * - * @example - * {"name": "not-ok.md", "label": "input", "gfm": true} - * - * http://foo.bar/baz - * - * @example - * {"name": "not-ok.md", "label": "output", "gfm": true} - * - * 1:1-1:19: Don’t use literal URLs without angle brackets - */ + let seenDelimiter; + /** @type {boolean|undefined} */ -const remarkLintNoLiteralUrls = lintRule( - { - origin: 'remark-lint:no-literal-urls', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-literal-urls#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'link', (node) => { - const value = toString(node); + let hasDash; + return start + /** @type {State} */ - if ( - !generated(node) && - pointStart(node).column === pointStart(node.children[0]).column && - pointEnd(node).column === - pointEnd(node.children[node.children.length - 1]).column && - (node.url === 'mailto:' + value || node.url === value) - ) { - file.message('Don’t use literal URLs without angle brackets', node); - } - }); - } -); + function start(code) { + // @ts-expect-error Custom. + effects.enter('table')._align = align; + effects.enter('tableHead'); + effects.enter('tableRow'); // If we start with a pipe, we open a cell marker. -var remarkLintNoLiteralUrls$1 = remarkLintNoLiteralUrls; + if (code === 124) { + return cellDividerHead(code) + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module ordered-list-marker-style - * @fileoverview - * Warn when the list item marker style of ordered lists violate a given style. - * - * Options: `'consistent'`, `'.'`, or `')'`, default: `'consistent'`. - * - * `'consistent'` detects the first used list style and warns when subsequent - * lists use different styles. - * - * @example - * {"name": "ok.md"} - * - * 1. Foo - * - * - * 1. Bar - * - * Unordered lists are not affected by this rule. - * - * * Foo - * - * @example - * {"name": "ok.md", "setting": "."} - * - * 1. Foo - * - * 2. Bar - * - * @example - * {"name": "ok.md", "setting": ")"} - * - * 1) Foo - * - * 2) Bar - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * 1. Foo - * - * 2) Bar - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 3:1-3:8: Marker style should be `.` - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} - * - * 1:1: Incorrect ordered list item marker style `💩`: use either `'.'` or `')'` - */ + tableHeaderCount++; + effects.enter('temporaryTableCellContent'); // Can’t be space or eols at the start of a construct, so we’re in a cell. -const remarkLintOrderedListMarkerStyle = lintRule( - { - origin: 'remark-lint:ordered-list-marker-style', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-ordered-list-marker-style#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); + return inCellContentHead(code) + } + /** @type {State} */ - if (option !== 'consistent' && option !== '.' && option !== ')') { - file.fail( - 'Incorrect ordered list item marker style `' + - option + - "`: use either `'.'` or `')'`" - ); - } + function cellDividerHead(code) { + effects.enter('tableCellDivider'); + effects.consume(code); + effects.exit('tableCellDivider'); + seenDelimiter = true; + return cellBreakHead + } + /** @type {State} */ - visit$1(tree, 'list', (node) => { - let index = -1; + function cellBreakHead(code) { + if (code === null || markdownLineEnding(code)) { + return atRowEndHead(code) + } - if (!node.ordered) return + if (markdownSpace(code)) { + effects.enter('whitespace'); + effects.consume(code); + return inWhitespaceHead + } - while (++index < node.children.length) { - const child = node.children[index]; + if (seenDelimiter) { + seenDelimiter = undefined; + tableHeaderCount++; + } - if (!generated(child)) { - const marker = /** @type {Marker} */ ( - value - .slice( - pointStart(child).offset, - pointStart(child.children[0]).offset - ) - .replace(/\s|\d/g, '') - .replace(/\[[x ]?]\s*$/i, '') - ); + if (code === 124) { + return cellDividerHead(code) + } // Anything else is cell content. - if (option === 'consistent') { - option = marker; - } else if (marker !== option) { - file.message('Marker style should be `' + option + '`', child); - } - } - } - }); + effects.enter('temporaryTableCellContent'); + return inCellContentHead(code) } -); + /** @type {State} */ -var remarkLintOrderedListMarkerStyle$1 = remarkLintOrderedListMarkerStyle; + function inWhitespaceHead(code) { + if (markdownSpace(code)) { + effects.consume(code); + return inWhitespaceHead + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module hard-break-spaces - * @fileoverview - * Warn when too many spaces are used to create a hard break. - * - * @example - * {"name": "ok.md"} - * - * Lorem ipsum·· - * dolor sit amet - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * Lorem ipsum··· - * dolor sit amet. - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:12-2:1: Use two spaces for hard line breaks - */ + effects.exit('whitespace'); + return cellBreakHead(code) + } + /** @type {State} */ -const remarkLintHardBreakSpaces = lintRule( - { - origin: 'remark-lint:hard-break-spaces', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-hard-break-spaces#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - const value = String(file); + function inCellContentHead(code) { + // EOF, whitespace, pipe + if (code === null || code === 124 || markdownLineEndingOrSpace(code)) { + effects.exit('temporaryTableCellContent'); + return cellBreakHead(code) + } - visit$1(tree, 'break', (node) => { - if (!generated(node)) { - const slice = value - .slice(pointStart(node).offset, pointEnd(node).offset) - .split('\n', 1)[0] - .replace(/\r$/, ''); + effects.consume(code); + return code === 92 ? inCellContentEscapeHead : inCellContentHead + } + /** @type {State} */ - if (slice.length > 2) { - file.message('Use two spaces for hard line breaks', node); - } - } - }); - } -); + function inCellContentEscapeHead(code) { + if (code === 92 || code === 124) { + effects.consume(code); + return inCellContentHead + } // Anything else. -var remarkLintHardBreakSpaces$1 = remarkLintHardBreakSpaces; + return inCellContentHead(code) + } + /** @type {State} */ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-duplicate-definitions - * @fileoverview - * Warn when duplicate definitions are found. - * - * @example - * {"name": "ok.md"} - * - * [foo]: bar - * [baz]: qux - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * [foo]: bar - * [foo]: qux - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 2:1-2:11: Do not use definitions with the same identifier (1:1) - */ + function atRowEndHead(code) { + if (code === null) { + return nok(code) + } -const remarkLintNoDuplicateDefinitions = lintRule( - { - origin: 'remark-lint:no-duplicate-definitions', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-duplicate-definitions#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - /** @type {Record} */ - const map = Object.create(null); + effects.exit('tableRow'); + effects.exit('tableHead'); + return effects.attempt( + { + tokenize: tokenizeRowEnd, + partial: true + }, + atDelimiterLineStart, + nok + )(code) + } + /** @type {State} */ - visit$1(tree, (node) => { - if ( - (node.type === 'definition' || node.type === 'footnoteDefinition') && - !generated(node) - ) { - const identifier = node.identifier; - const duplicate = map[identifier]; + function atDelimiterLineStart(code) { + // To do: is the lazy setext thing still needed? + return effects.check( + setextUnderlineMini, + nok, // Support an indent before the delimiter row. + factorySpace(effects, rowStartDelimiter, 'linePrefix', 4) + )(code) + } + /** @type {State} */ - if (duplicate) { - file.message( - 'Do not use definitions with the same identifier (' + - duplicate + - ')', - node - ); - } + function rowStartDelimiter(code) { + // If there’s another space, or we’re at the EOL/EOF, exit. + if (code === null || markdownLineEndingOrSpace(code)) { + return nok(code) + } - map[identifier] = stringifyPosition(pointStart(node)); - } - }); + effects.enter('tableDelimiterRow'); + return atDelimiterRowBreak(code) } -); + /** @type {State} */ -var remarkLintNoDuplicateDefinitions$1 = remarkLintNoDuplicateDefinitions; + function atDelimiterRowBreak(code) { + if (code === null || markdownLineEnding(code)) { + return rowEndDelimiter(code) + } -/** - * @typedef {import('mdast').Heading} Heading - * @typedef {'atx'|'atx-closed'|'setext'} Style - */ + if (markdownSpace(code)) { + effects.enter('whitespace'); + effects.consume(code); + return inWhitespaceDelimiter + } -/** - * @param {Heading} node - * @param {Style} [relative] - * @returns {Style|null} - */ -function headingStyle(node, relative) { - var last = node.children[node.children.length - 1]; - var depth = node.depth; - var pos = node && node.position && node.position.end; - var final = last && last.position && last.position.end; + if (code === 45) { + effects.enter('tableDelimiterFiller'); + effects.consume(code); + hasDash = true; + align.push(null); + return inFillerDelimiter + } - if (!pos) { - return null + if (code === 58) { + effects.enter('tableDelimiterAlignment'); + effects.consume(code); + effects.exit('tableDelimiterAlignment'); + align.push('left'); + return afterLeftAlignment + } // If we start with a pipe, we open a cell marker. + + if (code === 124) { + effects.enter('tableCellDivider'); + effects.consume(code); + effects.exit('tableCellDivider'); + return atDelimiterRowBreak + } + + return nok(code) } + /** @type {State} */ - // This can only occur for `'atx'` and `'atx-closed'` headings. - // This might incorrectly match `'atx'` headings with lots of trailing white - // space as an `'atx-closed'` heading. - if (!last) { - if (pos.column - 1 <= depth * 2) { - return consolidate(depth, relative) + function inWhitespaceDelimiter(code) { + if (markdownSpace(code)) { + effects.consume(code); + return inWhitespaceDelimiter } - return 'atx-closed' + effects.exit('whitespace'); + return atDelimiterRowBreak(code) } + /** @type {State} */ - if (final.line + 1 === pos.line) { - return 'setext' + function inFillerDelimiter(code) { + if (code === 45) { + effects.consume(code); + return inFillerDelimiter + } + + effects.exit('tableDelimiterFiller'); + + if (code === 58) { + effects.enter('tableDelimiterAlignment'); + effects.consume(code); + effects.exit('tableDelimiterAlignment'); + align[align.length - 1] = + align[align.length - 1] === 'left' ? 'center' : 'right'; + return afterRightAlignment + } + + return atDelimiterRowBreak(code) } + /** @type {State} */ - if (final.column + depth < pos.column) { - return 'atx-closed' + function afterLeftAlignment(code) { + if (code === 45) { + effects.enter('tableDelimiterFiller'); + effects.consume(code); + hasDash = true; + return inFillerDelimiter + } // Anything else is not ok. + + return nok(code) } + /** @type {State} */ - return consolidate(depth, relative) -} + function afterRightAlignment(code) { + if (code === null || markdownLineEnding(code)) { + return rowEndDelimiter(code) + } -/** - * Get the probable style of an atx-heading, depending on preferred style. - * - * @param {number} depth - * @param {Style} relative - * @returns {Style|null} - */ -function consolidate(depth, relative) { - return depth < 3 - ? 'atx' - : relative === 'atx' || relative === 'setext' - ? relative - : null -} + if (markdownSpace(code)) { + effects.enter('whitespace'); + effects.consume(code); + return inWhitespaceDelimiter + } // `|` -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-heading-content-indent - * @fileoverview - * Warn when content of headings is indented. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * removes all unneeded padding around content in headings. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * #·Foo - * - * ## Bar·## - * - * ##·Baz - * - * Setext headings are not affected. - * - * Baz - * === - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * #··Foo - * - * ## Bar··## - * - * ##··Baz - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:4: Remove 1 space before this heading’s content - * 3:7: Remove 1 space after this heading’s content - * 5:7: Remove 1 space before this heading’s content - * - * @example - * {"name": "empty-heading.md"} - * - * #·· - */ - -const remarkLintNoHeadingContentIndent = lintRule( - { - origin: 'remark-lint:no-heading-content-indent', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-heading-content-indent#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'heading', (node) => { - if (generated(node)) { - return - } + if (code === 124) { + effects.enter('tableCellDivider'); + effects.consume(code); + effects.exit('tableCellDivider'); + return atDelimiterRowBreak + } - const type = headingStyle(node, 'atx'); + return nok(code) + } + /** @type {State} */ - if (type === 'atx' || type === 'atx-closed') { - const head = pointStart(node.children[0]).column; + function rowEndDelimiter(code) { + effects.exit('tableDelimiterRow'); // Exit if there was no dash at all, or if the header cell count is not the + // delimiter cell count. - // Ignore empty headings. - if (!head) { - return - } + if (!hasDash || tableHeaderCount !== align.length) { + return nok(code) + } - const diff = head - pointStart(node).column - 1 - node.depth; + if (code === null) { + return tableClose(code) + } - if (diff) { - file.message( - 'Remove ' + - Math.abs(diff) + - ' ' + - plural('space', Math.abs(diff)) + - ' before this heading’s content', - pointStart(node.children[0]) - ); - } - } + return effects.check( + nextPrefixedOrBlank, + tableClose, + effects.attempt( + { + tokenize: tokenizeRowEnd, + partial: true + }, + factorySpace(effects, bodyStart, 'linePrefix', 4), + tableClose + ) + )(code) + } + /** @type {State} */ - // Closed ATX headings always must have a space between their content and - // the final hashes, thus, there is no `add x spaces`. - if (type === 'atx-closed') { - const final = pointEnd(node.children[node.children.length - 1]); - const diff = pointEnd(node).column - final.column - 1 - node.depth; + function tableClose(code) { + effects.exit('table'); + return ok(code) + } + /** @type {State} */ - if (diff) { - file.message( - 'Remove ' + - diff + - ' ' + - plural('space', diff) + - ' after this heading’s content', - final - ); - } - } - }); + function bodyStart(code) { + effects.enter('tableBody'); + return rowStartBody(code) } -); + /** @type {State} */ -var remarkLintNoHeadingContentIndent$1 = remarkLintNoHeadingContentIndent; + function rowStartBody(code) { + effects.enter('tableRow'); // If we start with a pipe, we open a cell marker. -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-inline-padding - * @fileoverview - * Warn when phrasing content is padded with spaces between their markers and - * content. - * - * Warns for emphasis, strong, delete, image, and link. - * - * @example - * {"name": "ok.md"} - * - * Alpha [bravo](http://echo.fox/trot) - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * Alpha [ bravo ](http://echo.fox/trot) - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:7-1:38: Don’t pad `link` with inner spaces - */ + if (code === 124) { + return cellDividerBody(code) + } -const remarkLintNoInlinePadding = lintRule( - { - origin: 'remark-lint:no-inline-padding', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-inline-padding#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - // Note: `emphasis`, `strong`, `delete` (GFM) can’t have padding anymore - // since CM. - visit$1(tree, (node) => { - if ( - (node.type === 'link' || node.type === 'linkReference') && - !generated(node) - ) { - const value = toString(node); + effects.enter('temporaryTableCellContent'); // Can’t be space or eols at the start of a construct, so we’re in a cell. - if (value.charAt(0) === ' ' || value.charAt(value.length - 1) === ' ') { - file.message('Don’t pad `' + node.type + '` with inner spaces', node); - } - } - }); + return inCellContentBody(code) } -); + /** @type {State} */ -var remarkLintNoInlinePadding$1 = remarkLintNoInlinePadding; + function cellDividerBody(code) { + effects.enter('tableCellDivider'); + effects.consume(code); + effects.exit('tableCellDivider'); + return cellBreakBody + } + /** @type {State} */ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-shortcut-reference-image - * @fileoverview - * Warn when shortcut reference images are used. - * - * Shortcut references render as images when a definition is found, and as - * plain text without definition. - * Sometimes, you don’t intend to create an image from the reference, but this - * rule still warns anyway. - * In that case, you can escape the reference like so: `!\[foo]`. - * - * @example - * {"name": "ok.md"} - * - * ![foo][] - * - * [foo]: http://foo.bar/baz.png - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * ![foo] - * - * [foo]: http://foo.bar/baz.png - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-1:7: Use the trailing [] on reference images - */ + function cellBreakBody(code) { + if (code === null || markdownLineEnding(code)) { + return atRowEndBody(code) + } -const remarkLintNoShortcutReferenceImage = lintRule( - { - origin: 'remark-lint:no-shortcut-reference-image', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-shortcut-reference-image#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'imageReference', (node) => { - if (!generated(node) && node.referenceType === 'shortcut') { - file.message('Use the trailing [] on reference images', node); - } - }); + if (markdownSpace(code)) { + effects.enter('whitespace'); + effects.consume(code); + return inWhitespaceBody + } // `|` + + if (code === 124) { + return cellDividerBody(code) + } // Anything else is cell content. + + effects.enter('temporaryTableCellContent'); + return inCellContentBody(code) } -); + /** @type {State} */ -var remarkLintNoShortcutReferenceImage$1 = remarkLintNoShortcutReferenceImage; + function inWhitespaceBody(code) { + if (markdownSpace(code)) { + effects.consume(code); + return inWhitespaceBody + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module no-shortcut-reference-link - * @fileoverview - * Warn when shortcut reference links are used. - * - * Shortcut references render as links when a definition is found, and as - * plain text without definition. - * Sometimes, you don’t intend to create a link from the reference, but this - * rule still warns anyway. - * In that case, you can escape the reference like so: `\[foo]`. - * - * @example - * {"name": "ok.md"} - * - * [foo][] - * - * [foo]: http://foo.bar/baz - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * [foo] - * - * [foo]: http://foo.bar/baz - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-1:6: Use the trailing `[]` on reference links - */ - -const remarkLintNoShortcutReferenceLink = lintRule( - { - origin: 'remark-lint:no-shortcut-reference-link', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-shortcut-reference-link#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - visit$1(tree, 'linkReference', (node) => { - if (!generated(node) && node.referenceType === 'shortcut') { - file.message('Use the trailing `[]` on reference links', node); - } - }); + effects.exit('whitespace'); + return cellBreakBody(code) } -); - -var remarkLintNoShortcutReferenceLink$1 = remarkLintNoShortcutReferenceLink; + /** @type {State} */ -/** - * @author Titus Wormer - * @copyright 2016 Titus Wormer - * @license MIT - * @module no-undefined-references - * @fileoverview - * Warn when references to undefined definitions are found. - * - * Options: `Object`, optional. - * - * The object can have an `allow` field, set to an array of strings that may - * appear between `[` and `]`, but that should not be treated as link - * identifiers. - * - * @example - * {"name": "ok.md"} - * - * [foo][] - * - * Just a [ bracket. - * - * Typically, you’d want to use escapes (with a backslash: \\) to escape what - * could turn into a \[reference otherwise]. - * - * Just two braces can’t link: []. - * - * [foo]: https://example.com - * - * @example - * {"name": "ok-allow.md", "setting": {"allow": ["...", "…"]}} - * - * > Eliding a portion of a quoted passage […] is acceptable. - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * [bar] - * - * [baz][] - * - * [text][qux] - * - * Spread [over - * lines][] - * - * > in [a - * > block quote][] - * - * [asd][a - * - * Can include [*emphasis*]. - * - * Multiple pairs: [a][b][c]. - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-1:6: Found reference to undefined definition - * 3:1-3:8: Found reference to undefined definition - * 5:1-5:12: Found reference to undefined definition - * 7:8-8:9: Found reference to undefined definition - * 10:6-11:17: Found reference to undefined definition - * 13:1-13:6: Found reference to undefined definition - * 15:13-15:25: Found reference to undefined definition - * 17:17-17:23: Found reference to undefined definition - * 17:23-17:26: Found reference to undefined definition - */ + function inCellContentBody(code) { + // EOF, whitespace, pipe + if (code === null || code === 124 || markdownLineEndingOrSpace(code)) { + effects.exit('temporaryTableCellContent'); + return cellBreakBody(code) + } -const remarkLintNoUndefinedReferences = lintRule( - { - origin: 'remark-lint:no-undefined-references', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-undefined-references#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = {}) => { - const contents = String(file); - const loc = location(file); - const lineEnding = /(\r?\n|\r)[\t ]*(>[\t ]*)*/g; - const allow = new Set( - (option.allow || []).map((d) => normalizeIdentifier(d)) - ); - /** @type {Record} */ - const map = Object.create(null); + effects.consume(code); + return code === 92 ? inCellContentEscapeBody : inCellContentBody + } + /** @type {State} */ - visit$1(tree, (node) => { - if ( - (node.type === 'definition' || node.type === 'footnoteDefinition') && - !generated(node) - ) { - map[normalizeIdentifier(node.identifier)] = true; - } - }); + function inCellContentEscapeBody(code) { + if (code === 92 || code === 124) { + effects.consume(code); + return inCellContentBody + } // Anything else. - visit$1(tree, (node) => { - // CM specifiers that references only form when defined. - // Still, they could be added by plugins, so let’s keep it. - /* c8 ignore next 10 */ - if ( - (node.type === 'imageReference' || - node.type === 'linkReference' || - node.type === 'footnoteReference') && - !generated(node) && - !(normalizeIdentifier(node.identifier) in map) && - !allow.has(normalizeIdentifier(node.identifier)) - ) { - file.message('Found reference to undefined definition', node); - } + return inCellContentBody(code) + } + /** @type {State} */ - if (node.type === 'paragraph' || node.type === 'heading') { - findInPhrasing(node); - } - }); + function atRowEndBody(code) { + effects.exit('tableRow'); - /** - * @param {Heading|Paragraph} node - */ - function findInPhrasing(node) { - /** @type {Range[]} */ - let ranges = []; + if (code === null) { + return tableBodyClose(code) + } - visit$1(node, (child) => { - // Ignore the node itself. - if (child === node) return + return effects.check( + nextPrefixedOrBlank, + tableBodyClose, + effects.attempt( + { + tokenize: tokenizeRowEnd, + partial: true + }, + factorySpace(effects, rowStartBody, 'linePrefix', 4), + tableBodyClose + ) + )(code) + } + /** @type {State} */ - // Can’t have links in links, so reset ranges. - if (child.type === 'link' || child.type === 'linkReference') { - ranges = []; - return SKIP$1 - } + function tableBodyClose(code) { + effects.exit('tableBody'); + return tableClose(code) + } + /** @type {Tokenizer} */ - // Enter non-text. - if (child.type !== 'text') return + function tokenizeRowEnd(effects, ok, nok) { + return start + /** @type {State} */ - const start = pointStart(child).offset; - const end = pointEnd(child).offset; + function start(code) { + effects.enter('lineEnding'); + effects.consume(code); + effects.exit('lineEnding'); + return lineStart + } + /** @type {State} */ - // Bail if there’s no positional info. - if (typeof start !== 'number' || typeof end !== 'number') { - return EXIT$1 - } + function lineStart(code) { + return self.parser.lazy[self.now().line] ? nok(code) : ok(code) + } + } +} // Based on micromark, but that won’t work as we’re in a table, and that expects +// content. +// - const source = contents.slice(start, end); - /** @type {Array.<[number, string]>} */ - const lines = [[start, '']]; - let last = 0; +/** @type {Tokenizer} */ - lineEnding.lastIndex = 0; - let match = lineEnding.exec(source); +function tokenizeSetextUnderlineMini(effects, ok, nok) { + return start + /** @type {State} */ - while (match) { - const index = match.index; - lines[lines.length - 1][1] = source.slice(last, index); - last = index + match[0].length; - lines.push([start + last, '']); - match = lineEnding.exec(source); - } + function start(code) { + if (code !== 45) { + return nok(code) + } - lines[lines.length - 1][1] = source.slice(last); - let lineIndex = -1; + effects.enter('setextUnderline'); + return sequence(code) + } + /** @type {State} */ - while (++lineIndex < lines.length) { - const line = lines[lineIndex][1]; - let index = 0; - - while (index < line.length) { - const code = line.charCodeAt(index); - - // Skip past escaped brackets. - if (code === 92) { - const next = line.charCodeAt(index + 1); - index++; + function sequence(code) { + if (code === 45) { + effects.consume(code); + return sequence + } - if (next === 91 || next === 93) { - index++; - } - } - // Opening bracket. - else if (code === 91) { - ranges.push([lines[lineIndex][0] + index]); - index++; - } - // Close bracket. - else if (code === 93) { - // No opening. - if (ranges.length === 0) { - index++; - } else if (line.charCodeAt(index + 1) === 91) { - index++; + return whitespace(code) + } + /** @type {State} */ - // Collapsed or full. - let range = ranges.pop(); + function whitespace(code) { + if (code === null || markdownLineEnding(code)) { + return ok(code) + } - // Range should always exist. - // eslint-disable-next-line max-depth - if (range) { - range.push(lines[lineIndex][0] + index); + if (markdownSpace(code)) { + effects.consume(code); + return whitespace + } - // This is the end of a reference already. - // eslint-disable-next-line max-depth - if (range.length === 4) { - handleRange(range); - range = []; - } + return nok(code) + } +} +/** @type {Tokenizer} */ - range.push(lines[lineIndex][0] + index); - ranges.push(range); - index++; - } - } else { - index++; +function tokenizeNextPrefixedOrBlank(effects, ok, nok) { + let size = 0; + return start + /** @type {State} */ - // Shortcut or typical end of a reference. - const range = ranges.pop(); + function start(code) { + // This is a check, so we don’t care about tokens, but we open a bogus one + // so we’re valid. + effects.enter('check'); // EOL. - // Range should always exist. - // eslint-disable-next-line max-depth - if (range) { - range.push(lines[lineIndex][0] + index); - handleRange(range); - } - } - } - // Anything else. - else { - index++; - } - } - } - }); + effects.consume(code); + return whitespace + } + /** @type {State} */ - let index = -1; + function whitespace(code) { + if (code === -1 || code === 32) { + effects.consume(code); + size++; + return size === 4 ? ok : whitespace + } // EOF or whitespace - while (++index < ranges.length) { - handleRange(ranges[index]); - } + if (code === null || markdownLineEndingOrSpace(code)) { + return ok(code) + } // Anything else. - return SKIP$1 + return nok(code) + } +} - /** - * @param {Range} range - */ - function handleRange(range) { - if (range.length === 1) return - if (range.length === 3) range.length = 2; +/** + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension + * @typedef {import('micromark-util-types').Token} Token + * @typedef {import('micromark-util-types').CompileContext} CompileContext + */ - // No need to warn for just `[]`. - if (range.length === 2 && range[0] + 2 === range[1]) return +/** + * An opening or closing tag, followed by a case-insensitive specific tag name, + * followed by HTML whitespace, a greater than, or a slash. + */ +const reFlow = + /<(\/?)(iframe|noembed|noframes|plaintext|script|style|title|textarea|xmp)(?=[\t\n\f\r />])/gi; - const offset = range.length === 4 && range[2] + 2 !== range[3] ? 2 : 0; - const id = contents - .slice(range[0 + offset] + 1, range[1 + offset] - 1) - .replace(lineEnding, ' '); - const pos = { - start: loc.toPoint(range[0]), - end: loc.toPoint(range[range.length - 1]) - }; +/** + * As HTML (text) parses tags separately (and v. strictly), we don’t need to be + * global. + */ +const reText = new RegExp('^' + reFlow.source, 'i'); - if ( - !generated({position: pos}) && - !(normalizeIdentifier(id) in map) && - !allow.has(normalizeIdentifier(id)) - ) { - file.message('Found reference to undefined definition', pos); - } - } +/** @type {HtmlExtension} */ +const gfmTagfilterHtml = { + exit: { + htmlFlowData(token) { + exitHtmlData.call(this, token, reFlow); + }, + htmlTextData(token) { + exitHtmlData.call(this, token, reText); } } -); - -var remarkLintNoUndefinedReferences$1 = remarkLintNoUndefinedReferences; +}; /** - * @author Titus Wormer - * @copyright 2016 Titus Wormer - * @license MIT - * @module no-unused-definitions - * @fileoverview - * Warn when unused definitions are found. - * - * @example - * {"name": "ok.md"} - * - * [foo][] - * - * [foo]: https://example.com - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * [bar]: https://example.com - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-1:27: Found unused definition + * @this {CompileContext} + * @param {Token} token + * @param {RegExp} filter */ +function exitHtmlData(token, filter) { + let value = this.sliceSerialize(token); -const own$2 = {}.hasOwnProperty; - -const remarkLintNoUnusedDefinitions = lintRule( - { - origin: 'remark-lint:no-unused-definitions', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-unused-definitions#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - /** @type {Record} */ - const map = Object.create(null); - - visit$1(tree, (node) => { - if ( - (node.type === 'definition' || node.type === 'footnoteDefinition') && - !generated(node) - ) { - map[node.identifier.toUpperCase()] = {node, used: false}; - } - }); - - visit$1(tree, (node) => { - if ( - node.type === 'imageReference' || - node.type === 'linkReference' || - node.type === 'footnoteReference' - ) { - const info = map[node.identifier.toUpperCase()]; + if (this.options.allowDangerousHtml) { + value = value.replace(filter, '<$1$2'); + } - if (!generated(node) && info) { - info.used = true; - } - } - }); + this.raw(this.encode(value)); +} - /** @type {string} */ - let identifier; +/** + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension + */ - for (identifier in map) { - if (own$2.call(map, identifier)) { - const entry = map[identifier]; +/** @type {HtmlExtension} */ +const gfmTaskListItemHtml = { + enter: { + taskListCheck() { + this.tag(''); + }, - if (!entry.used) { - file.message('Found unused definition', entry.node); - } - } + taskListCheckValueChecked() { + this.tag('checked="" '); } } -); - -var remarkLintNoUnusedDefinitions$1 = remarkLintNoUnusedDefinitions; +}; /** - * @fileoverview - * remark preset to configure `remark-lint` with settings that prevent - * mistakes or stuff that fails across vendors. + * @typedef {import('micromark-util-types').Extension} Extension + * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord + * @typedef {import('micromark-util-types').Tokenizer} Tokenizer + * @typedef {import('micromark-util-types').Previous} Previous + * @typedef {import('micromark-util-types').State} State + * @typedef {import('micromark-util-types').Event} Event + * @typedef {import('micromark-util-types').Code} Code */ - -/** @type {Preset} */ -const remarkPresetLintRecommended = { - plugins: [ - remarkLint, - // Unix compatibility. - remarkLintFinalNewline$1, - // Rendering across vendors differs greatly if using other styles. - remarkLintListItemBulletIndent$1, - [remarkLintListItemIndent$1, 'tab-size'], - // Differs or unsupported across vendors. - remarkLintNoAutoLinkWithoutProtocol$1, - remarkLintNoBlockquoteWithoutMarker$1, - remarkLintNoLiteralUrls$1, - [remarkLintOrderedListMarkerStyle$1, '.'], - // Mistakes. - remarkLintHardBreakSpaces$1, - remarkLintNoDuplicateDefinitions$1, - remarkLintNoHeadingContentIndent$1, - remarkLintNoInlinePadding$1, - remarkLintNoShortcutReferenceImage$1, - remarkLintNoShortcutReferenceLink$1, - remarkLintNoUndefinedReferences$1, - remarkLintNoUnusedDefinitions$1 - ] +const tasklistCheck = { + tokenize: tokenizeTasklistCheck +}; +const gfmTaskListItem = { + text: { + [91]: tasklistCheck + } }; +/** @type {Tokenizer} */ -var remarkPresetLintRecommended$1 = remarkPresetLintRecommended; +function tokenizeTasklistCheck(effects, ok, nok) { + const self = this; + return open + /** @type {State} */ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module blockquote-indentation - * @fileoverview - * Warn when block quotes are indented too much or too little. - * - * Options: `number` or `'consistent'`, default: `'consistent'`. - * - * `'consistent'` detects the first used indentation and will warn when - * other block quotes use a different indentation. - * - * @example - * {"name": "ok.md", "setting": 4} - * - * > Hello - * - * Paragraph. - * - * > World - * @example - * {"name": "ok.md", "setting": 2} - * - * > Hello - * - * Paragraph. - * - * > World - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * > Hello - * - * Paragraph. - * - * > World - * - * Paragraph. - * - * > World - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 5:5: Remove 1 space between block quote and content - * 9:3: Add 1 space between block quote and content - */ + function open(code) { + if ( + // Exit if there’s stuff before. + self.previous !== null || // Exit if not in the first content that is the first child of a list + // item. + !self._gfmTasklistFirstContentOfListItem + ) { + return nok(code) + } -const remarkLintBlockquoteIndentation = lintRule( - { - origin: 'remark-lint:blockquote-indentation', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-blockquote-indentation#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - visit$1(tree, 'blockquote', (node) => { - if (generated(node) || node.children.length === 0) { - return - } + effects.enter('taskListCheck'); + effects.enter('taskListCheckMarker'); + effects.consume(code); + effects.exit('taskListCheckMarker'); + return inside + } + /** @type {State} */ - if (option === 'consistent') { - option = check$1(node); - } else { - const diff = option - check$1(node); + function inside(code) { + if (markdownSpace(code)) { + effects.enter('taskListCheckValueUnchecked'); + effects.consume(code); + effects.exit('taskListCheckValueUnchecked'); + return close + } - if (diff !== 0) { - const abs = Math.abs(diff); + if (code === 88 || code === 120) { + effects.enter('taskListCheckValueChecked'); + effects.consume(code); + effects.exit('taskListCheckValueChecked'); + return close + } - file.message( - (diff > 0 ? 'Add' : 'Remove') + - ' ' + - abs + - ' ' + - plural('space', abs) + - ' between block quote and content', - pointStart(node.children[0]) - ); - } - } - }); + return nok(code) } -); + /** @type {State} */ -var remarkLintBlockquoteIndentation$1 = remarkLintBlockquoteIndentation; + function close(code) { + if (code === 93) { + effects.enter('taskListCheckMarker'); + effects.consume(code); + effects.exit('taskListCheckMarker'); + effects.exit('taskListCheck'); + return effects.check( + { + tokenize: spaceThenNonSpace + }, + ok, + nok + ) + } + + return nok(code) + } +} +/** @type {Tokenizer} */ + +function spaceThenNonSpace(effects, ok, nok) { + const self = this; + return factorySpace(effects, after, 'whitespace') + /** @type {State} */ + + function after(code) { + const tail = self.events[self.events.length - 1]; + return tail && + tail[1].type === 'whitespace' && + code !== null && + !markdownLineEndingOrSpace(code) + ? ok(code) + : nok(code) + } +} /** - * @param {Blockquote} node - * @returns {number} + * @typedef {import('micromark-util-types').Extension} Extension + * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension + * @typedef {import('micromark-extension-gfm-strikethrough').Options} Options */ -function check$1(node) { - return pointStart(node.children[0]).column - pointStart(node).column -} /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module checkbox-character-style - * @fileoverview - * Warn when list item checkboxes violate a given style. - * - * Options: `Object` or `'consistent'`, default: `'consistent'`. - * - * `'consistent'` detects the first used checked and unchecked checkbox - * styles and warns when subsequent checkboxes use different styles. - * - * Styles can also be passed in like so: - * - * ```js - * {checked: 'x', unchecked: ' '} - * ``` - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats checked checkboxes using `x` (lowercase X) and unchecked checkboxes - * as `·` (a single space). - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md", "setting": {"checked": "x"}, "gfm": true} + * Support GFM or markdown on github.com. * - * - [x] List item - * - [x] List item + * @param {Options} [options] + * @returns {Extension} + */ +function gfm(options) { + return combineExtensions([ + gfmAutolinkLiteral, + gfmStrikethrough(options), + gfmTable, + gfmTaskListItem + ]) +} + +/** @type {HtmlExtension} */ +combineHtmlExtensions([ + gfmAutolinkLiteralHtml, + gfmStrikethroughHtml, + gfmTableHtml, + gfmTagfilterHtml, + gfmTaskListItemHtml +]); + +/** + * Get the total count of `character` in `value`. * - * @example - * {"name": "ok.md", "setting": {"checked": "X"}, "gfm": true} - * - * - [X] List item - * - [X] List item - * - * @example - * {"name": "ok.md", "setting": {"unchecked": " "}, "gfm": true} - * - * - [ ] List item - * - [ ] List item - * - [ ]·· - * - [ ] - * - * @example - * {"name": "ok.md", "setting": {"unchecked": "\t"}, "gfm": true} - * - * - [»] List item - * - [»] List item - * - * @example - * {"name": "not-ok.md", "label": "input", "gfm": true} - * - * - [x] List item - * - [X] List item - * - [ ] List item - * - [»] List item - * - * @example - * {"name": "not-ok.md", "label": "output", "gfm": true} - * - * 2:5: Checked checkboxes should use `x` as a marker - * 4:5: Unchecked checkboxes should use ` ` as a marker - * - * @example - * {"setting": {"unchecked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true} - * - * 1:1: Incorrect unchecked checkbox marker `💩`: use either `'\t'`, or `' '` - * - * @example - * {"setting": {"checked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true} - * - * 1:1: Incorrect checked checkbox marker `💩`: use either `'x'`, or `'X'` + * @param {any} value Content, coerced to string + * @param {string} character Single character to look for + * @return {number} Number of times `character` occurred in `value`. */ +function ccount(value, character) { + var source = String(value); + var count = 0; + var index; -const remarkLintCheckboxCharacterStyle = lintRule( - { - origin: 'remark-lint:checkbox-character-style', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-checkbox-character-style#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); - /** @type {'x'|'X'|'consistent'} */ - let checked = 'consistent'; - /** @type {' '|'\x09'|'consistent'} */ - let unchecked = 'consistent'; - - if (typeof option === 'object') { - checked = option.checked || 'consistent'; - unchecked = option.unchecked || 'consistent'; - } - - if (unchecked !== 'consistent' && unchecked !== ' ' && unchecked !== '\t') { - file.fail( - 'Incorrect unchecked checkbox marker `' + - unchecked + - "`: use either `'\\t'`, or `' '`" - ); - } - - if (checked !== 'consistent' && checked !== 'x' && checked !== 'X') { - file.fail( - 'Incorrect checked checkbox marker `' + - checked + - "`: use either `'x'`, or `'X'`" - ); - } - - visit$1(tree, 'listItem', (node) => { - const head = node.children[0]; - const point = pointStart(head); - - // Exit early for items without checkbox. - // A list item cannot be checked and empty, according to GFM. - if ( - typeof node.checked !== 'boolean' || - !head || - typeof point.offset !== 'number' - ) { - return - } - - // Move back to before `] `. - point.offset -= 2; - point.column -= 2; + if (typeof character !== 'string') { + throw new Error('Expected character') + } - // Assume we start with a checkbox, because well, `checked` is set. - const match = /\[([\t Xx])]/.exec( - value.slice(point.offset - 2, point.offset + 1) - ); + index = source.indexOf(character); - // Failsafe to make sure we don‘t crash if there actually isn’t a checkbox. - /* c8 ignore next */ - if (!match) return + while (index !== -1) { + count++; + index = source.indexOf(character, index + character.length); + } - const style = node.checked ? checked : unchecked; + return count +} - if (style === 'consistent') { - if (node.checked) { - // @ts-expect-error: valid marker. - checked = match[1]; - } else { - // @ts-expect-error: valid marker. - unchecked = match[1]; - } - } else if (match[1] !== style) { - file.message( - (node.checked ? 'Checked' : 'Unchecked') + - ' checkboxes should use `' + - style + - '` as a marker', - point - ); - } - }); - } -); +function escapeStringRegexp(string) { + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } -var remarkLintCheckboxCharacterStyle$1 = remarkLintCheckboxCharacterStyle; + // Escape characters with special meaning either inside or outside character sets. + // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. + return string + .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + .replace(/-/g, '\\x2d'); +} /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module checkbox-content-indent - * @fileoverview - * Warn when list item checkboxes are followed by too much whitespace. - * - * @example - * {"name": "ok.md", "gfm": true} - * - * - [ ] List item - * + [x] List Item - * * [X] List item - * - [ ] List item - * - * @example - * {"name": "not-ok.md", "label": "input", "gfm": true} - * - * - [ ] List item - * + [x] List item - * * [X] List item - * - [ ] List item - * - * @example - * {"name": "not-ok.md", "label": "output", "gfm": true} - * - * 2:7-2:8: Checkboxes should be followed by a single character - * 3:7-3:9: Checkboxes should be followed by a single character - * 4:7-4:10: Checkboxes should be followed by a single character + * @param {string} d + * @returns {string} */ +function color(d) { + return '\u001B[33m' + d + '\u001B[39m' +} -const remarkLintCheckboxContentIndent = lintRule( - { - origin: 'remark-lint:checkbox-content-indent', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-checkbox-content-indent#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - const value = String(file); - const loc = location(file); +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + * @typedef {import('unist-util-is').Test} Test + */ - visit$1(tree, 'listItem', (node) => { - const head = node.children[0]; - const point = pointStart(head); +/** + * Continue traversing as normal + */ +const CONTINUE = true; +/** + * Do not traverse this node’s children + */ +const SKIP = 'skip'; +/** + * Stop traversing immediately + */ +const EXIT = false; - // Exit early for items without checkbox. - // A list item cannot be checked and empty, according to GFM. - if ( - typeof node.checked !== 'boolean' || - !head || - typeof point.offset !== 'number' - ) { - return +const visitParents = + /** + * @type {( + * ((tree: Node, test: T['type']|Partial|import('unist-util-is').TestFunctionPredicate|Array.|import('unist-util-is').TestFunctionPredicate>, visitor: Visitor, reverse?: boolean) => void) & + * ((tree: Node, test: Test, visitor: Visitor, reverse?: boolean) => void) & + * ((tree: Node, visitor: Visitor, reverse?: boolean) => void) + * )} + */ + ( + /** + * Visit children of tree which pass a test + * + * @param {Node} tree Abstract syntax tree to walk + * @param {Test} test test Test node + * @param {Visitor} visitor Function to run for each node + * @param {boolean} [reverse] Fisit the tree in reverse, defaults to false + */ + function (tree, test, visitor, reverse) { + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor; + // @ts-ignore no visitor given, so `visitor` is test. + visitor = test; + test = null; } - // Assume we start with a checkbox, because well, `checked` is set. - const match = /\[([\t xX])]/.exec( - value.slice(point.offset - 4, point.offset + 1) - ); + var is = convert(test); + var step = reverse ? -1 : 1; - // Failsafe to make sure we don‘t crash if there actually isn’t a checkbox. - /* c8 ignore next */ - if (!match) return + factory(tree, null, [])(); - // Move past checkbox. - const initial = point.offset; - let final = initial; + /** + * @param {Node} node + * @param {number?} index + * @param {Array.} parents + */ + function factory(node, index, parents) { + /** @type {Object.} */ + var value = typeof node === 'object' && node !== null ? node : {}; + /** @type {string} */ + var name; - while (/[\t ]/.test(value.charAt(final))) final++; + if (typeof value.type === 'string') { + name = + typeof value.tagName === 'string' + ? value.tagName + : typeof value.name === 'string' + ? value.name + : undefined; - if (final - initial > 0) { - file.message('Checkboxes should be followed by a single character', { - start: loc.toPoint(initial), - end: loc.toPoint(final) - }); - } - }); - } -); + Object.defineProperty(visit, 'name', { + value: + 'node (' + + color(value.type + (name ? '<' + name + '>' : '')) + + ')' + }); + } -var remarkLintCheckboxContentIndent$1 = remarkLintCheckboxContentIndent; + return visit -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module code-block-style - * @fileoverview - * Warn when code blocks do not adhere to a given style. - * - * Options: `'consistent'`, `'fenced'`, or `'indented'`, default: `'consistent'`. - * - * `'consistent'` detects the first used code block style and warns when - * subsequent code blocks uses different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats code blocks using a fence if they have a language flag and - * indentation if not. - * Pass - * [`fences: true`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsfences) - * to always use fences for code blocks. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"setting": "indented", "name": "ok.md"} - * - * alpha() - * - * Paragraph. - * - * bravo() - * - * @example - * {"setting": "indented", "name": "not-ok.md", "label": "input"} - * - * ``` - * alpha() - * ``` - * - * Paragraph. - * - * ``` - * bravo() - * ``` - * - * @example - * {"setting": "indented", "name": "not-ok.md", "label": "output"} - * - * 1:1-3:4: Code blocks should be indented - * 7:1-9:4: Code blocks should be indented - * - * @example - * {"setting": "fenced", "name": "ok.md"} - * - * ``` - * alpha() - * ``` - * - * Paragraph. - * - * ``` - * bravo() - * ``` - * - * @example - * {"setting": "fenced", "name": "not-ok-fenced.md", "label": "input"} - * - * alpha() - * - * Paragraph. - * - * bravo() - * - * @example - * {"setting": "fenced", "name": "not-ok-fenced.md", "label": "output"} - * - * 1:1-1:12: Code blocks should be fenced - * 5:1-5:12: Code blocks should be fenced - * - * @example - * {"name": "not-ok-consistent.md", "label": "input"} - * - * alpha() - * - * Paragraph. - * - * ``` - * bravo() - * ``` - * - * @example - * {"name": "not-ok-consistent.md", "label": "output"} - * - * 5:1-7:4: Code blocks should be indented - * - * @example - * {"setting": "💩", "name": "not-ok-incorrect.md", "label": "output", "positionless": true} - * - * 1:1: Incorrect code block style `💩`: use either `'consistent'`, `'fenced'`, or `'indented'` - */ + function visit() { + /** @type {ActionTuple} */ + var result = []; + /** @type {ActionTuple} */ + var subresult; + /** @type {number} */ + var offset; + /** @type {Array.} */ + var grandparents; -const remarkLintCodeBlockStyle = lintRule( - { - origin: 'remark-lint:code-block-style', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-code-block-style#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); + if (!test || is(node, index, parents[parents.length - 1] || null)) { + result = toResult(visitor(node, parents)); - if ( - option !== 'consistent' && - option !== 'fenced' && - option !== 'indented' - ) { - file.fail( - 'Incorrect code block style `' + - option + - "`: use either `'consistent'`, `'fenced'`, or `'indented'`" - ); - } + if (result[0] === EXIT) { + return result + } + } - visit$1(tree, 'code', (node) => { - if (generated(node)) { - return - } + if (node.children && result[0] !== SKIP) { + // @ts-ignore looks like a parent. + offset = (reverse ? node.children.length : -1) + step; + // @ts-ignore looks like a parent. + grandparents = parents.concat(node); - const initial = pointStart(node).offset; - const final = pointEnd(node).offset; + // @ts-ignore looks like a parent. + while (offset > -1 && offset < node.children.length) { + subresult = factory(node.children[offset], offset, grandparents)(); - const current = - node.lang || /^\s*([~`])\1{2,}/.test(value.slice(initial, final)) - ? 'fenced' - : 'indented'; + if (subresult[0] === EXIT) { + return subresult + } - if (option === 'consistent') { - option = current; - } else if (option !== current) { - file.message('Code blocks should be ' + option, node); + offset = + typeof subresult[1] === 'number' ? subresult[1] : offset + step; + } + } + + return result + } } - }); + } + ); + +/** + * @param {VisitorResult} value + * @returns {ActionTuple} + */ +function toResult(value) { + if (Array.isArray(value)) { + return value } -); -var remarkLintCodeBlockStyle$1 = remarkLintCodeBlockStyle; + if (typeof value === 'number') { + return [CONTINUE, value] + } + + return [value] +} /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module definition-spacing - * @fileoverview - * Warn when consecutive whitespace is used in a definition. + * @typedef Options Configuration. + * @property {Test} [ignore] `unist-util-is` test used to assert parents * - * @example - * {"name": "ok.md"} + * @typedef {import('mdast').Root} Root + * @typedef {import('mdast').Content} Content + * @typedef {import('mdast').PhrasingContent} PhrasingContent + * @typedef {import('mdast').Text} Text + * @typedef {Content|Root} Node + * @typedef {Extract} Parent * - * [example domain]: http://example.com "Example Domain" + * @typedef {import('unist-util-visit-parents').Test} Test + * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult * - * @example - * {"name": "not-ok.md", "label": "input"} + * @typedef RegExpMatchObject + * @property {number} index + * @property {string} input * - * [example····domain]: http://example.com "Example Domain" + * @typedef {string|RegExp} Find + * @typedef {string|ReplaceFunction} Replace * - * @example - * {"name": "not-ok.md", "label": "output"} + * @typedef {[Find, Replace]} FindAndReplaceTuple + * @typedef {Object.} FindAndReplaceSchema + * @typedef {Array.} FindAndReplaceList * - * 1:1-1:57: Do not use consecutive whitespace in definition labels + * @typedef {[RegExp, ReplaceFunction]} Pair + * @typedef {Array.} Pairs */ -const label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/; +const own$3 = {}.hasOwnProperty; -const remarkLintDefinitionSpacing = lintRule( - { - origin: 'remark-lint:definition-spacing', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-definition-spacing#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - const value = String(file); +/** + * @param tree mdast tree + * @param find Value to find and remove. When `string`, escaped and made into a global `RegExp` + * @param [replace] Value to insert. + * * When `string`, turned into a Text node. + * * When `Function`, called with the results of calling `RegExp.exec` as + * arguments, in which case it can return a single or a list of `Node`, + * a `string` (which is wrapped in a `Text` node), or `false` to not replace + * @param [options] Configuration. + */ +const findAndReplace = + /** + * @type {( + * ((tree: Node, find: Find, replace?: Replace, options?: Options) => Node) & + * ((tree: Node, schema: FindAndReplaceSchema|FindAndReplaceList, options?: Options) => Node) + * )} + **/ + ( + /** + * @param {Node} tree + * @param {Find|FindAndReplaceSchema|FindAndReplaceList} find + * @param {Replace|Options} [replace] + * @param {Options} [options] + */ + function (tree, find, replace, options) { + /** @type {Options|undefined} */ + let settings; + /** @type {FindAndReplaceSchema|FindAndReplaceList} */ + let schema; - visit$1(tree, (node) => { - if (node.type === 'definition' || node.type === 'footnoteDefinition') { - const start = pointStart(node).offset; - const end = pointEnd(node).offset; + if (typeof find === 'string' || find instanceof RegExp) { + // @ts-expect-error don’t expect options twice. + schema = [[find, replace]]; + settings = options; + } else { + schema = find; + // @ts-expect-error don’t expect replace twice. + settings = replace; + } - if (typeof start === 'number' && typeof end === 'number') { - const match = value.slice(start, end).match(label); + if (!settings) { + settings = {}; + } - if (match && /[ \t\n]{2,}/.test(match[1])) { - file.message( - 'Do not use consecutive whitespace in definition labels', - node - ); - } - } + const ignored = convert(settings.ignore || []); + const pairs = toPairs(schema); + let pairIndex = -1; + + while (++pairIndex < pairs.length) { + visitParents(tree, 'text', visitor); } - }); - } -); -var remarkLintDefinitionSpacing$1 = remarkLintDefinitionSpacing; + return tree -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module fenced-code-flag - * @fileoverview - * Check fenced code block flags. - * - * Options: `Array.` or `Object`, optional. - * - * Providing an array is as passing `{flags: Array}`. - * - * The object can have an array of `'flags'` which are allowed: other flags - * will not be allowed. - * An `allowEmpty` field (`boolean`, default: `false`) can be set to allow - * code blocks without language flags. - * - * @example - * {"name": "ok.md"} - * - * ```alpha - * bravo() - * ``` - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * ``` - * alpha() - * ``` - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:1-3:4: Missing code language flag - * - * @example - * {"name": "ok.md", "setting": {"allowEmpty": true}} - * - * ``` - * alpha() - * ``` - * - * @example - * {"name": "not-ok.md", "setting": {"allowEmpty": false}, "label": "input"} - * - * ``` - * alpha() - * ``` - * - * @example - * {"name": "not-ok.md", "setting": {"allowEmpty": false}, "label": "output"} - * - * 1:1-3:4: Missing code language flag - * - * @example - * {"name": "ok.md", "setting": ["alpha"]} - * - * ```alpha - * bravo() - * ``` - * - * @example - * {"name": "ok.md", "setting": {"flags":["alpha"]}} - * - * ```alpha - * bravo() - * ``` - * - * @example - * {"name": "not-ok.md", "setting": ["charlie"], "label": "input"} - * - * ```alpha - * bravo() - * ``` - * - * @example - * {"name": "not-ok.md", "setting": ["charlie"], "label": "output"} - * - * 1:1-3:4: Incorrect code language flag - */ + /** @type {import('unist-util-visit-parents').Visitor} */ + function visitor(node, parents) { + let index = -1; + /** @type {Parent|undefined} */ + let grandparent; -const fence = /^ {0,3}([~`])\1{2,}/; + while (++index < parents.length) { + const parent = /** @type {Parent} */ (parents[index]); -const remarkLintFencedCodeFlag = lintRule( - { - origin: 'remark-lint:fenced-code-flag', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-fenced-code-flag#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option) => { - const value = String(file); - let allowEmpty = false; - /** @type {string[]} */ - let allowed = []; + if ( + ignored( + parent, + // @ts-expect-error mdast vs. unist parent. + grandparent ? grandparent.children.indexOf(parent) : undefined, + grandparent + ) + ) { + return + } - if (typeof option === 'object') { - if (Array.isArray(option)) { - allowed = option; - } else { - allowEmpty = Boolean(option.allowEmpty); + grandparent = parent; + } - if (option.flags) { - allowed = option.flags; + if (grandparent) { + return handler(node, grandparent) } } - } - visit$1(tree, 'code', (node) => { - if (!generated(node)) { - if (node.lang) { - if (allowed.length > 0 && !allowed.includes(node.lang)) { - file.message('Incorrect code language flag', node); - } - } else { - const slice = value.slice( - pointStart(node).offset, - pointEnd(node).offset - ); + /** + * @param {Text} node + * @param {Parent} parent + * @returns {VisitorResult} + */ + function handler(node, parent) { + const find = pairs[pairIndex][0]; + const replace = pairs[pairIndex][1]; + let start = 0; + // @ts-expect-error: TS is wrong, some of these children can be text. + let index = parent.children.indexOf(node); + /** @type {Array.} */ + let nodes = []; + /** @type {number|undefined} */ + let position; - if (!allowEmpty && fence.test(slice)) { - file.message('Missing code language flag', node); + find.lastIndex = 0; + + let match = find.exec(node.value); + + while (match) { + position = match.index; + // @ts-expect-error this is perfectly fine, typescript. + let value = replace(...match, { + index: match.index, + input: match.input + }); + + if (typeof value === 'string') { + value = value.length > 0 ? {type: 'text', value} : undefined; } - } - } - }); - } -); -var remarkLintFencedCodeFlag$1 = remarkLintFencedCodeFlag; + if (value !== false) { + if (start !== position) { + nodes.push({ + type: 'text', + value: node.value.slice(start, position) + }); + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module fenced-code-marker - * @fileoverview - * Warn for violating fenced code markers. - * - * Options: `` '`' ``, `'~'`, or `'consistent'`, default: `'consistent'`. - * - * `'consistent'` detects the first used fenced code marker style and warns - * when subsequent fenced code blocks use different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats fences using ``'`'`` (grave accent) by default. - * Pass - * [`fence: '~'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsfence) - * to use `~` (tilde) instead. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * Indented code blocks are not affected by this rule: - * - * bravo() - * - * @example - * {"name": "ok.md", "setting": "`"} - * - * ```alpha - * bravo() - * ``` - * - * ``` - * charlie() - * ``` - * - * @example - * {"name": "ok.md", "setting": "~"} - * - * ~~~alpha - * bravo() - * ~~~ - * - * ~~~ - * charlie() - * ~~~ - * - * @example - * {"name": "not-ok-consistent-tick.md", "label": "input"} - * - * ```alpha - * bravo() - * ``` - * - * ~~~ - * charlie() - * ~~~ - * - * @example - * {"name": "not-ok-consistent-tick.md", "label": "output"} - * - * 5:1-7:4: Fenced code should use `` ` `` as a marker - * - * @example - * {"name": "not-ok-consistent-tilde.md", "label": "input"} - * - * ~~~alpha - * bravo() - * ~~~ - * - * ``` - * charlie() - * ``` - * - * @example - * {"name": "not-ok-consistent-tilde.md", "label": "output"} - * - * 5:1-7:4: Fenced code should use `~` as a marker - * - * @example - * {"name": "not-ok-incorrect.md", "setting": "💩", "label": "output", "positionless": true} - * - * 1:1: Incorrect fenced code marker `💩`: use either `'consistent'`, `` '`' ``, or `'~'` - */ - -const remarkLintFencedCodeMarker = lintRule( - { - origin: 'remark-lint:fenced-code-marker', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-fenced-code-marker#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const contents = String(file); + if (Array.isArray(value)) { + nodes.push(...value); + } else if (value) { + nodes.push(value); + } - if (option !== 'consistent' && option !== '~' && option !== '`') { - file.fail( - 'Incorrect fenced code marker `' + - option + - "`: use either `'consistent'`, `` '`' ``, or `'~'`" - ); - } + start = position + match[0].length; + } - visit$1(tree, 'code', (node) => { - const start = pointStart(node).offset; + if (!find.global) { + break + } - if (typeof start === 'number') { - const marker = contents - .slice(start, start + 4) - .replace(/^\s+/, '') - .charAt(0); + match = find.exec(node.value); + } - // Ignore unfenced code blocks. - if (marker === '~' || marker === '`') { - if (option === 'consistent') { - option = marker; - } else if (marker !== option) { - file.message( - 'Fenced code should use `' + - (option === '~' ? option : '` ` `') + - '` as a marker', - node - ); + if (position === undefined) { + nodes = [node]; + index--; + } else { + if (start < node.value.length) { + nodes.push({type: 'text', value: node.value.slice(start)}); } + + parent.children.splice(index, 1, ...nodes); } - } - }); - } -); -var remarkLintFencedCodeMarker$1 = remarkLintFencedCodeMarker; + return index + nodes.length + 1 + } + } + ); /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module file-extension - * @fileoverview - * Warn when the file extension differ from the preferred extension. - * - * Does not warn when given documents have no file extensions (such as - * `AUTHORS` or `LICENSE`). - * - * Options: `string`, default: `'md'` — Expected file extension. - * - * @example - * {"name": "readme.md"} - * - * @example - * {"name": "readme"} - * - * @example - * {"name": "readme.mkd", "label": "output", "positionless": true} - * - * 1:1: Incorrect extension: use `md` - * - * @example - * {"name": "readme.mkd", "setting": "mkd"} + * @param {FindAndReplaceSchema|FindAndReplaceList} schema + * @returns {Pairs} */ +function toPairs(schema) { + /** @type {Pairs} */ + const result = []; -const remarkLintFileExtension = lintRule( - { - origin: 'remark-lint:file-extension', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-file-extension#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (_, file, option = 'md') => { - const ext = file.extname; + if (typeof schema !== 'object') { + throw new TypeError('Expected array or object as schema') + } - if (ext && ext.slice(1) !== option) { - file.message('Incorrect extension: use `' + option + '`'); + if (Array.isArray(schema)) { + let index = -1; + + while (++index < schema.length) { + result.push([ + toExpression(schema[index][0]), + toFunction(schema[index][1]) + ]); + } + } else { + /** @type {string} */ + let key; + + for (key in schema) { + if (own$3.call(schema, key)) { + result.push([toExpression(key), toFunction(schema[key])]); + } } } -); -var remarkLintFileExtension$1 = remarkLintFileExtension; + return result +} /** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module final-definition - * @fileoverview - * Warn when definitions are placed somewhere other than at the end of - * the file. - * - * @example - * {"name": "ok.md"} - * - * Paragraph. - * - * [example]: http://example.com "Example Domain" - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * Paragraph. - * - * [example]: http://example.com "Example Domain" - * - * Another paragraph. - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 3:1-3:47: Move definitions to the end of the file (after the node at line `5`) - * - * @example - * {"name": "ok-comments.md"} - * - * Paragraph. - * - * [example-1]: http://example.com/one/ - * - * - * - * [example-2]: http://example.com/two/ + * @param {Find} find + * @returns {RegExp} */ +function toExpression(find) { + return typeof find === 'string' ? new RegExp(escapeStringRegexp(find), 'g') : find +} -const remarkLintFinalDefinition = lintRule( - { - origin: 'remark-lint:final-definition', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-definition#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - let last = 0; +/** + * @param {Replace} replace + * @returns {ReplaceFunction} + */ +function toFunction(replace) { + return typeof replace === 'function' ? replace : () => replace +} - visit$1( - tree, - (node) => { - // Ignore generated and HTML comment nodes. - if ( - node.type === 'root' || - generated(node) || - (node.type === 'html' && /^\s* + * ``` + * @property {MarkerParser} marker + * Parse a possible marker to a comment marker object (Marker). + * If the marker isn't a marker, should return `null`. + * @property {Test} [test] + * Test for possible markers + * @property {string[]} [known] + * List of allowed `ruleId`s. When given a warning is shown + * when someone tries to control an unknown rule. + * + * For example, `{name: 'alpha', known: ['bravo']}` results in a warning if + * `charlie` is configured: + * + * ```html + * + * ``` + * @property {string|string[]} [source] + * Sources that can be controlled with `name` markers. + * Defaults to `name`. + * + * @callback MarkerParser + * Parse a possible comment marker node to a Marker. + * @param {Node} node + * Node to parse + * + * @typedef Marker + * A comment marker. + * @property {string} name + * Name of marker. + * @property {string} attributes + * Value after name. + * @property {Record} parameters + * Parsed attributes. + * @property {Node} node + * Reference to given node. + * + * @typedef Mark + * @property {Point|undefined} point + * @property {boolean} state + */ -function factory(id, rule) { - var parts = id.split(':'); - var source = parts[0]; - var ruleId = parts[1]; - var fn = wrapped(rule); +const own$2 = {}.hasOwnProperty; - /* istanbul ignore if - possibly useful if externalised later. */ - if (!ruleId) { - ruleId = source; - source = null; +/** + * @type {import('unified').Plugin<[Options]>} + * @returns {(tree: Node, file: VFile) => void} + */ +function messageControl(options) { + if (!options || typeof options !== 'object' || !options.name) { + throw new Error( + 'Expected `name` in `options`, got `' + (options || {}).name + '`' + ) } - attacher.displayName = id; + if (!options.marker) { + throw new Error( + 'Expected `marker` in `options`, got `' + options.marker + '`' + ) + } - return attacher + const enable = 'enable' in options && options.enable ? options.enable : []; + const disable = 'disable' in options && options.disable ? options.disable : []; + let reset = options.reset; + const sources = + typeof options.source === 'string' + ? [options.source] + : options.source || [options.name]; - function attacher(raw) { - var config = coerce(ruleId, raw); - var severity = config[0]; - var options = config[1]; - var fatal = severity === 2; + return transformer - return severity ? transformer : undefined + /** + * @param {Node} tree + * @param {VFile} file + */ + function transformer(tree, file) { + const toOffset = location(file).toOffset; + const initial = !reset; + const gaps = detectGaps(tree, file); + /** @type {Record} */ + const scope = {}; + /** @type {Mark[]} */ + const globals = []; - function transformer(tree, file, next) { - var index = file.messages.length; + visit(tree, options.test, visitor); - fn(tree, file, options, done); + file.messages = file.messages.filter((m) => filter(m)); - function done(err) { - var messages = file.messages; - var message; + /** + * @param {Node} node + * @param {number|null} position + * @param {Parent|null} parent + */ + function visitor(node, position, parent) { + /** @type {Marker|null} */ + const mark = options.marker(node); - // Add the error, if not already properly added. - /* istanbul ignore if - only happens for incorrect plugins */ - if (err && messages.indexOf(err) === -1) { - try { - file.fail(err); - } catch (_) {} - } + if (!mark || mark.name !== options.name) { + return + } - while (index < messages.length) { - message = messages[index]; - message.ruleId = ruleId; - message.source = source; - message.fatal = fatal; + const ruleIds = mark.attributes.split(/\s/g); + const point = mark.node.position && mark.node.position.start; + const next = + (parent && position !== null && parent.children[position + 1]) || + undefined; + const tail = (next && next.position && next.position.end) || undefined; + let index = -1; - index++; - } + /** @type {string} */ + // @ts-expect-error: we’ll check for unknown values next. + const verb = ruleIds.shift(); - next(); + if (verb !== 'enable' && verb !== 'disable' && verb !== 'ignore') { + file.fail( + 'Unknown keyword `' + + verb + + '`: expected ' + + "`'enable'`, `'disable'`, or `'ignore'`", + mark.node + ); } - } - } -} - -// Coerce a value to a severity--options tuple. -function coerce(name, value) { - var def = 1; - var result; - var level; - /* istanbul ignore if - Handled by unified in v6.0.0 */ - if (typeof value === 'boolean') { - result = [value]; - } else if (value == null) { - result = [def]; - } else if ( - typeof value === 'object' && - (typeof value[0] === 'number' || - typeof value[0] === 'boolean' || - typeof value[0] === 'string') - ) { - result = value.concat(); - } else { - result = [1, value]; - } + // Apply to all rules. + if (ruleIds.length > 0) { + while (++index < ruleIds.length) { + const ruleId = ruleIds[index]; - level = result[0]; + if (isKnown(ruleId, verb, mark.node)) { + toggle(point, verb === 'enable', ruleId); - if (typeof level === 'boolean') { - level = level ? 1 : 0; - } else if (typeof level === 'string') { - if (level === 'off') { - level = 0; - } else if (level === 'on' || level === 'warn') { - level = 1; - } else if (level === 'error') { - level = 2; - } else { - level = 1; - result = [level, result]; + if (verb === 'ignore') { + toggle(tail, true, ruleId); + } + } + } + } else if (verb === 'ignore') { + toggle(point, false); + toggle(tail, true); + } else { + toggle(point, verb === 'enable'); + reset = verb !== 'enable'; + } } - } - - if (level < 0 || level > 2) { - throw new Error( - 'Incorrect severity `' + - level + - '` for `' + - name + - '`, ' + - 'expected 0, 1, or 2' - ) - } - - result[0] = level; - return result -} + /** + * @param {VFileMessage} message + * @returns {boolean} + */ + function filter(message) { + let gapIndex = gaps.length; -var rule = unifiedLintRule; + // Keep messages from a different source. + if (!message.source || !sources.includes(message.source)) { + return true + } -var remarkLintNoTrailingSpaces = rule('remark-lint:no-trailing-spaces', noTrailingSpaces); + // We only ignore messages if they‘re disabled, *not* when they’re not in + // the document. + if (!message.line) { + message.line = 1; + } -/** - * Lines that are just space characters are not present in - * the AST, which is why we loop through lines manually. - */ + if (!message.column) { + message.column = 1; + } -function noTrailingSpaces(ast, file) { - var lines = file.toString().split(/\r?\n/); - for (var i = 0; i < lines.length; i++) { - var currentLine = lines[i]; - var lineIndex = i + 1; - if (/\s$/.test(currentLine)) { - file.message('Remove trailing whitespace', { - position: { - start: { line: lineIndex, column: currentLine.length + 1 }, - end: { line: lineIndex } + // Check whether the warning is inside a gap. + // @ts-expect-error: we just normalized `null` to `number`s. + const offset = toOffset(message); + + while (gapIndex--) { + if (gaps[gapIndex][0] <= offset && gaps[gapIndex][1] > offset) { + return false } - }); + } + + // Check whether allowed by specific and global states. + return ( + (!message.ruleId || + check(message, scope[message.ruleId], message.ruleId)) && + check(message, globals) + ) } - } -} -function* getLinksRecursively(node) { - if (node.url) { - yield node; - } - for (const child of node.children || []) { - yield* getLinksRecursively(child); - } -} + /** + * Helper to check (and possibly warn) if a `ruleId` is unknown. + * + * @param {string} ruleId + * @param {string} verb + * @param {Node} node + * @returns {boolean} + */ + function isKnown(ruleId, verb, node) { + const result = options.known ? options.known.includes(ruleId) : true; -function validateLinks(tree, vfile) { - const currentFileURL = pathToFileURL(path$1.join(vfile.cwd, vfile.path)); - let previousDefinitionLabel; - for (const node of getLinksRecursively(tree)) { - if (node.url[0] !== "#") { - const targetURL = new URL(node.url, currentFileURL); - if (targetURL.protocol === "file:" && !fs.existsSync(targetURL)) { - vfile.message("Broken link", node); - } else if (targetURL.pathname === currentFileURL.pathname) { - const expected = node.url.includes("#") - ? node.url.slice(node.url.indexOf("#")) - : "#"; - vfile.message( - `Self-reference must start with hash (expected "${expected}", got "${node.url}")`, + if (!result) { + file.message( + 'Unknown rule: cannot ' + verb + " `'" + ruleId + "'`", node ); } + + return result } - if (node.type === "definition") { - if (previousDefinitionLabel && previousDefinitionLabel > node.label) { - vfile.message( - `Unordered reference ("${node.label}" should be before "${previousDefinitionLabel}")`, - node - ); + + /** + * Get the latest state of a rule. + * When without `ruleId`, gets global state. + * + * @param {string|undefined} ruleId + * @returns {boolean} + */ + function getState(ruleId) { + const ranges = ruleId ? scope[ruleId] : globals; + + if (ranges && ranges.length > 0) { + return ranges[ranges.length - 1].state } - previousDefinitionLabel = node.label; - } - } -} -const remarkLintNodejsLinks = lintRule( - "remark-lint:nodejs-links", - validateLinks -); + if (!ruleId) { + return !reset + } -/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ -function isNothing(subject) { - return (typeof subject === 'undefined') || (subject === null); -} + return reset ? enable.includes(ruleId) : !disable.includes(ruleId) + } + /** + * Handle a rule. + * + * @param {Point|undefined} point + * @param {boolean} state + * @param {string|undefined} [ruleId] + * @returns {void} + */ + function toggle(point, state, ruleId) { + let markers = ruleId ? scope[ruleId] : globals; -function isObject(subject) { - return (typeof subject === 'object') && (subject !== null); -} + if (!markers) { + markers = []; + scope[String(ruleId)] = markers; + } + const previousState = getState(ruleId); -function toArray(sequence) { - if (Array.isArray(sequence)) return sequence; - else if (isNothing(sequence)) return []; + if (state !== previousState) { + markers.push({state, point}); + } - return [ sequence ]; -} + // Toggle all known rules. + if (!ruleId) { + for (ruleId in scope) { + if (own$2.call(scope, ruleId)) { + toggle(point, state, ruleId); + } + } + } + } + + /** + * Check all `ranges` for `message`. + * + * @param {VFileMessage} message + * @param {Mark[]|undefined} ranges + * @param {string|undefined} [ruleId] + * @returns {boolean} + */ + function check(message, ranges, ruleId) { + if (ranges && ranges.length > 0) { + // Check the state at the message’s position. + let index = ranges.length; + while (index--) { + const range = ranges[index]; -function extend(target, source) { - var index, length, key, sourceKeys; + if ( + message.line && + message.column && + range.point && + range.point.line && + range.point.column && + (range.point.line < message.line || + (range.point.line === message.line && + range.point.column <= message.column)) + ) { + return range.state === true + } + } + } - if (source) { - sourceKeys = Object.keys(source); + // The first marker ocurred after the first message, so we check the + // initial state. + if (!ruleId) { + return Boolean(initial || reset) + } - for (index = 0, length = sourceKeys.length; index < length; index += 1) { - key = sourceKeys[index]; - target[key] = source[key]; + return reset ? enable.includes(ruleId) : !disable.includes(ruleId) } } - - return target; } +/** + * Detect gaps in `tree`. + * + * @param {Node} tree + * @param {VFile} file + */ +function detectGaps(tree, file) { + /** @type {Node[]} */ + // @ts-expect-error: fine. + const children = tree.children || []; + const lastNode = children[children.length - 1]; + /** @type {[number, number][]} */ + const gaps = []; + let offset = 0; + /** @type {boolean|undefined} */ + let gap; -function repeat(string, count) { - var result = '', cycle; + // Find all gaps. + visit(tree, one); - for (cycle = 0; cycle < count; cycle += 1) { - result += string; - } + // Get the end of the document. + // This detects if the last node was the last node. + // If not, there’s an extra gap between the last node and the end of the + // document. + if ( + lastNode && + lastNode.position && + lastNode.position.end && + offset === lastNode.position.end.offset && + file.toString().slice(offset).trim() !== '' + ) { + update(); - return result; -} + update( + tree && + tree.position && + tree.position.end && + tree.position.end.offset && + tree.position.end.offset - 1 + ); + } + return gaps -function isNegativeZero(number) { - return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number); -} + /** + * @param {Node} node + */ + function one(node) { + update(node.position && node.position.start && node.position.start.offset); + if (!('children' in node)) { + update(node.position && node.position.end && node.position.end.offset); + } + } -var isNothing_1 = isNothing; -var isObject_1 = isObject; -var toArray_1 = toArray; -var repeat_1 = repeat; -var isNegativeZero_1 = isNegativeZero; -var extend_1 = extend; - -var common = { - isNothing: isNothing_1, - isObject: isObject_1, - toArray: toArray_1, - repeat: repeat_1, - isNegativeZero: isNegativeZero_1, - extend: extend_1 -}; + /** + * Detect a new position. + * + * @param {number|undefined} [latest] + * @returns {void} + */ + function update(latest) { + if (latest === null || latest === undefined) { + gap = true; + } else if (offset < latest) { + if (gap) { + gaps.push([offset, latest]); + gap = undefined; + } -// YAML error class. http://stackoverflow.com/questions/8458984 + offset = latest; + } + } +} +/** + * @typedef {string|number|boolean} MarkerParameterValue + * @typedef {import('mdast').Root} Root + * @typedef {import('mdast').Content} Content + * @typedef {import('mdast').HTML} HTML + * @typedef {import('mdast-util-mdx-expression').MDXFlowExpression} MDXFlowExpression + * @typedef {import('mdast-util-mdx-expression').MDXTextExpression} MDXTextExpression + * @typedef {Root|Content} Node + * @typedef {Object.} MarkerParameters + * + * @typedef Mdx1CommentNode + * @property {'comment'} type + * @property {string} value + * + * @typedef Marker + * @property {string} name + * @property {string} attributes + * @property {MarkerParameters|null} parameters + * @property {HTML|Mdx1CommentNode|MDXFlowExpression|MDXTextExpression} node + */ -function formatError(exception, compact) { - var where = '', message = exception.reason || '(unknown reason)'; +const commentExpression = /\s*([a-zA-Z\d-]+)(\s+([\s\S]*))?\s*/; +const esCommentExpression = new RegExp( + '(\\s*\\/\\*' + commentExpression.source + '\\*\\/\\s*)' +); +const markerExpression = new RegExp( + '(\\s*\\s*)' +); - if (!exception.mark) return message; +/** + * Parse a comment marker. + * @param {unknown} value + * @returns {Marker|null} + */ +function commentMarker(value) { + if ( + isNode(value) && + (value.type === 'html' || + // @ts-expect-error: MDX@1 + value.type === 'comment' || + value.type === 'mdxFlowExpression' || + value.type === 'mdxTextExpression') + ) { + let offset = 2; + /** @type {RegExpMatchArray|null|undefined} */ + let match; - if (exception.mark.name) { - where += 'in "' + exception.mark.name + '" '; - } + // @ts-expect-error: MDX@1 + if (value.type === 'comment') { + // @ts-expect-error: MDX@1 + match = value.value.match(commentExpression); + offset = 1; + } else if (value.type === 'html') { + match = value.value.match(markerExpression); + } else if ( + value.type === 'mdxFlowExpression' || + value.type === 'mdxTextExpression' + ) { + match = value.value.match(esCommentExpression); + } - where += '(' + (exception.mark.line + 1) + ':' + (exception.mark.column + 1) + ')'; + if (match && match[0].length === value.value.length) { + const parameters = parseParameters(match[offset + 1] || ''); - if (!compact && exception.mark.snippet) { - where += '\n\n' + exception.mark.snippet; + if (parameters) { + return { + name: match[offset], + attributes: (match[offset + 2] || '').trim(), + parameters, + node: value + } + } + } } - return message + ' ' + where; + return null } +/** + * Parse `value` into an object. + * + * @param {string} value + * @returns {MarkerParameters|null} + */ +function parseParameters(value) { + /** @type {MarkerParameters} */ + const parameters = {}; -function YAMLException$1(reason, mark) { - // Super constructor - Error.call(this); - - this.name = 'YAMLException'; - this.reason = reason; - this.mark = mark; - this.message = formatError(this, false); - - // Include stack trace in error object - if (Error.captureStackTrace) { - // Chrome and NodeJS - Error.captureStackTrace(this, this.constructor); - } else { - // FF, IE 10+ and Safari 6+. Fallback for others - this.stack = (new Error()).stack || ''; - } -} + return value + .replace( + /\s+([-\w]+)(?:=(?:"((?:\\[\s\S]|[^"])+)"|'((?:\\[\s\S]|[^'])+)'|((?:\\[\s\S]|[^"'\s])+)))?/gi, + replacer + ) + .replace(/\s+/g, '') + ? null + : parameters + /** + * @param {string} _ + * @param {string} $1 + * @param {string} $2 + * @param {string} $3 + * @param {string} $4 + */ + // eslint-disable-next-line max-params + function replacer(_, $1, $2, $3, $4) { + /** @type {MarkerParameterValue} */ + let value = $2 || $3 || $4 || ''; -// Inherit from Error -YAMLException$1.prototype = Object.create(Error.prototype); -YAMLException$1.prototype.constructor = YAMLException$1; + if (value === 'true' || value === '') { + value = true; + } else if (value === 'false') { + value = false; + } else if (!Number.isNaN(Number(value))) { + value = Number(value); + } + parameters[$1] = value; -YAMLException$1.prototype.toString = function toString(compact) { - return this.name + ': ' + formatError(this, compact); -}; + return '' + } +} +/** + * @param {unknown} value + * @returns {value is Node} + */ +function isNode(value) { + return Boolean(value && typeof value === 'object' && 'type' in value) +} -var exception = YAMLException$1; +/** + * @typedef {import('mdast').Root} Root + * @typedef {import('vfile').VFile} VFile + * @typedef {import('unified-message-control')} MessageControl + * @typedef {Omit|Omit} Options + */ -// get snippet for a single line, respecting maxLength -function getLine(buffer, lineStart, lineEnd, position, maxLineLength) { - var head = ''; - var tail = ''; - var maxHalfLength = Math.floor(maxLineLength / 2) - 1; +const test = [ + 'html', // Comments are `html` nodes in mdast. + 'comment', // In MDX@1, comments have their own node. + 'mdxFlowExpression', // In MDX@2, comments exist in bracketed expressions. + 'mdxTextExpression' +]; - if (position - lineStart > maxHalfLength) { - head = ' ... '; - lineStart = position - maxHalfLength + head.length; - } +/** + * Plugin to enable, disable, and ignore messages. + * + * @type {import('unified').Plugin<[Options], Root>} + * @returns {(node: Root, file: VFile) => void} + */ +function remarkMessageControl(options) { + return messageControl( + Object.assign({marker: commentMarker, test}, options) + ) +} - if (lineEnd - position > maxHalfLength) { - tail = ' ...'; - lineEnd = position + maxHalfLength - tail.length; - } +/** + * @typedef {import('mdast').Root} Root + */ - return { - str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail, - pos: position - lineStart + head.length // relative position - }; +/** + * The core plugin for `remark-lint`. + * This adds support for ignoring stuff from messages (``). + * All rules are in their own packages and presets. + * + * @type {import('unified').Plugin} + */ +function remarkLint() { + this.use(lintMessageControl); } - -function padStart(string, max) { - return common.repeat(' ', max - string.length) + string; +/** @type {import('unified').Plugin} */ +function lintMessageControl() { + return remarkMessageControl({name: 'lint', source: 'remark-lint'}) } +/** + * @typedef {import('unist').Node} Node + * @typedef {import('vfile').VFile} VFile + * + * @typedef {0|1|2} Severity + * @typedef {'warn'|'on'|'off'|'error'} Label + * @typedef {[Severity, ...unknown[]]} SeverityTuple + * + * @typedef RuleMeta + * @property {string} origin name of the lint rule + * @property {string} [url] link to documentation + * + * @callback Rule + * @param {Node} tree + * @param {VFile} file + * @param {unknown} options + * @returns {void} + */ -function makeSnippet(mark, options) { - options = Object.create(options || null); +const primitives = new Set(['string', 'number', 'boolean']); - if (!mark.buffer) return null; +/** + * @param {string|RuleMeta} meta + * @param {Rule} rule + */ +function lintRule(meta, rule) { + const id = typeof meta === 'string' ? meta : meta.origin; + const url = typeof meta === 'string' ? undefined : meta.url; + const parts = id.split(':'); + // Possibly useful if externalised later. + /* c8 ignore next */ + const source = parts[1] ? parts[0] : undefined; + const ruleId = parts[1]; - if (!options.maxLength) options.maxLength = 79; - if (typeof options.indent !== 'number') options.indent = 1; - if (typeof options.linesBefore !== 'number') options.linesBefore = 3; - if (typeof options.linesAfter !== 'number') options.linesAfter = 2; + Object.defineProperty(plugin, 'name', {value: id}); - var re = /\r?\n|\r|\0/g; - var lineStarts = [ 0 ]; - var lineEnds = []; - var match; - var foundLineNo = -1; + return plugin - while ((match = re.exec(mark.buffer))) { - lineEnds.push(match.index); - lineStarts.push(match.index + match[0].length); + /** @type {import('unified').Plugin<[unknown]|void[]>} */ + function plugin(raw) { + const [severity, options] = coerce$1(ruleId, raw); - if (mark.position <= match.index && foundLineNo < 0) { - foundLineNo = lineStarts.length - 2; - } - } + if (!severity) return - if (foundLineNo < 0) foundLineNo = lineStarts.length - 1; + const fatal = severity === 2; - var result = '', i, line; - var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length; - var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3); + return (tree, file, next) => { + let index = file.messages.length - 1; - for (i = 1; i <= options.linesBefore; i++) { - if (foundLineNo - i < 0) break; - line = getLine( - mark.buffer, - lineStarts[foundLineNo - i], - lineEnds[foundLineNo - i], - mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]), - maxLineLength - ); - result = common.repeat(' ', options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n' + result; - } + wrap(rule, (error) => { + const messages = file.messages; - line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength); - result += common.repeat(' ', options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n'; - result += common.repeat('-', options.indent + lineNoLength + 3 + line.pos) + '^' + '\n'; + // Add the error, if not already properly added. + // Only happens for incorrect plugins. + /* c8 ignore next 6 */ + // @ts-expect-error: errors could be `messages`. + if (error && !messages.includes(error)) { + try { + file.fail(error); + } catch {} + } - for (i = 1; i <= options.linesAfter; i++) { - if (foundLineNo + i >= lineEnds.length) break; - line = getLine( - mark.buffer, - lineStarts[foundLineNo + i], - lineEnds[foundLineNo + i], - mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]), - maxLineLength - ); - result += common.repeat(' ', options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + - ' | ' + line.str + '\n'; - } + while (++index < messages.length) { + Object.assign(messages[index], {ruleId, source, fatal, url}); + } - return result.replace(/\n$/, ''); + next(); + })(tree, file, options); + } + } } +/** + * Coerce a value to a severity--options tuple. + * + * @param {string} name + * @param {unknown} value + * @returns {SeverityTuple} + */ +function coerce$1(name, value) { + /** @type {unknown[]} */ + let result; -var snippet = makeSnippet; - -var TYPE_CONSTRUCTOR_OPTIONS = [ - 'kind', - 'multi', - 'resolve', - 'construct', - 'instanceOf', - 'predicate', - 'represent', - 'representName', - 'defaultStyle', - 'styleAliases' -]; + if (typeof value === 'boolean') { + result = [value]; + } else if (value === null || value === undefined) { + result = [1]; + } else if ( + Array.isArray(value) && + // `isArray(unknown)` is turned into `any[]`: + // type-coverage:ignore-next-line + primitives.has(typeof value[0]) + ) { + // `isArray(unknown)` is turned into `any[]`: + // type-coverage:ignore-next-line + result = [...value]; + } else { + result = [1, value]; + } -var YAML_NODE_KINDS = [ - 'scalar', - 'sequence', - 'mapping' -]; + let level = result[0]; -function compileStyleAliases(map) { - var result = {}; + if (typeof level === 'boolean') { + level = level ? 1 : 0; + } else if (typeof level === 'string') { + if (level === 'off') { + level = 0; + } else if (level === 'on' || level === 'warn') { + level = 1; + } else if (level === 'error') { + level = 2; + } else { + level = 1; + result = [level, result]; + } + } - if (map !== null) { - Object.keys(map).forEach(function (style) { - map[style].forEach(function (alias) { - result[String(alias)] = style; - }); - }); + if (typeof level !== 'number' || level < 0 || level > 2) { + throw new Error( + 'Incorrect severity `' + + level + + '` for `' + + name + + '`, ' + + 'expected 0, 1, or 2' + ) } - return result; -} + result[0] = level; -function Type$1(tag, options) { - options = options || {}; + // @ts-expect-error: it’s now a valid tuple. + return result +} - Object.keys(options).forEach(function (name) { - if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { - throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); - } - }); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module final-newline + * @fileoverview + * Warn when a line feed at the end of a file is missing. + * Empty files are allowed. + * + * See [StackExchange](https://unix.stackexchange.com/questions/18743) for why. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * always adds a final line feed to files. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * ## Example + * + * ##### `ok.md` + * + * ###### In + * + * Note: `␊` represents LF. + * + * ```markdown + * Alpha␊ + * ``` + * + * ###### Out + * + * No messages. + * + * ##### `not-ok.md` + * + * ###### In + * + * Note: The below file does not have a final newline. + * + * ```markdown + * Bravo + * ``` + * + * ###### Out + * + * ```text + * 1:1: Missing newline character at end of file + * ``` + */ - // TODO: Add tag format check. - this.options = options; // keep original options in case user wants to extend this type later - this.tag = tag; - this.kind = options['kind'] || null; - this.resolve = options['resolve'] || function () { return true; }; - this.construct = options['construct'] || function (data) { return data; }; - this.instanceOf = options['instanceOf'] || null; - this.predicate = options['predicate'] || null; - this.represent = options['represent'] || null; - this.representName = options['representName'] || null; - this.defaultStyle = options['defaultStyle'] || null; - this.multi = options['multi'] || false; - this.styleAliases = compileStyleAliases(options['styleAliases'] || null); +const remarkLintFinalNewline = lintRule( + { + origin: 'remark-lint:final-newline', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-newline#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (_, file) => { + const value = String(file); + const last = value.length - 1; - if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { - throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + if (last > -1 && value.charAt(last) !== '\n') { + file.message('Missing newline character at end of file'); + } } -} +); -var type = Type$1; +var remarkLintFinalNewline$1 = remarkLintFinalNewline; -/*eslint-disable max-len*/ +var pluralize = {exports: {}}; +/* global define */ +(function (module, exports) { +(function (root, pluralize) { + /* istanbul ignore else */ + if (typeof commonjsRequire === 'function' && 'object' === 'object' && 'object' === 'object') { + // Node. + module.exports = pluralize(); + } else { + // Browser global. + root.pluralize = pluralize(); + } +})(commonjsGlobal, function () { + // Rule storage - pluralize and singularize need to be run sequentially, + // while other rules can be optimized using an object for instant lookups. + var pluralRules = []; + var singularRules = []; + var uncountables = {}; + var irregularPlurals = {}; + var irregularSingles = {}; + /** + * Sanitize a pluralization rule to a usable regular expression. + * + * @param {(RegExp|string)} rule + * @return {RegExp} + */ + function sanitizeRule (rule) { + if (typeof rule === 'string') { + return new RegExp('^' + rule + '$', 'i'); + } + return rule; + } -function compileList(schema, name) { - var result = []; - - schema[name].forEach(function (currentType) { - var newIndex = result.length; - - result.forEach(function (previousType, previousIndex) { - if (previousType.tag === currentType.tag && - previousType.kind === currentType.kind && - previousType.multi === currentType.multi) { - - newIndex = previousIndex; - } - }); - - result[newIndex] = currentType; - }); - - return result; -} + /** + * Pass in a word token to produce a function that can replicate the case on + * another word. + * + * @param {string} word + * @param {string} token + * @return {Function} + */ + function restoreCase (word, token) { + // Tokens are an exact match. + if (word === token) return token; + // Lower cased words. E.g. "hello". + if (word === word.toLowerCase()) return token.toLowerCase(); -function compileMap(/* lists... */) { - var result = { - scalar: {}, - sequence: {}, - mapping: {}, - fallback: {}, - multi: { - scalar: [], - sequence: [], - mapping: [], - fallback: [] - } - }, index, length; + // Upper cased words. E.g. "WHISKY". + if (word === word.toUpperCase()) return token.toUpperCase(); - function collectType(type) { - if (type.multi) { - result.multi[type.kind].push(type); - result.multi['fallback'].push(type); - } else { - result[type.kind][type.tag] = result['fallback'][type.tag] = type; + // Title cased words. E.g. "Title". + if (word[0] === word[0].toUpperCase()) { + return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase(); } - } - for (index = 0, length = arguments.length; index < length; index += 1) { - arguments[index].forEach(collectType); + // Lower cased words. E.g. "test". + return token.toLowerCase(); } - return result; -} + /** + * Interpolate a regexp string. + * + * @param {string} str + * @param {Array} args + * @return {string} + */ + function interpolate (str, args) { + return str.replace(/\$(\d{1,2})/g, function (match, index) { + return args[index] || ''; + }); + } -function Schema$1(definition) { - return this.extend(definition); -} - - -Schema$1.prototype.extend = function extend(definition) { - var implicit = []; - var explicit = []; - - if (definition instanceof type) { - // Schema.extend(type) - explicit.push(definition); - - } else if (Array.isArray(definition)) { - // Schema.extend([ type1, type2, ... ]) - explicit = explicit.concat(definition); + /** + * Replace a word using a rule. + * + * @param {string} word + * @param {Array} rule + * @return {string} + */ + function replace (word, rule) { + return word.replace(rule[0], function (match, index) { + var result = interpolate(rule[1], arguments); - } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { - // Schema.extend({ explicit: [ type1, type2, ... ], implicit: [ type1, type2, ... ] }) - if (definition.implicit) implicit = implicit.concat(definition.implicit); - if (definition.explicit) explicit = explicit.concat(definition.explicit); + if (match === '') { + return restoreCase(word[index - 1], result); + } - } else { - throw new exception('Schema.extend argument should be a Type, [ Type ], ' + - 'or a schema definition ({ implicit: [...], explicit: [...] })'); + return restoreCase(match, result); + }); } - implicit.forEach(function (type$1) { - if (!(type$1 instanceof type)) { - throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + /** + * Sanitize a word by passing in the word and sanitization rules. + * + * @param {string} token + * @param {string} word + * @param {Array} rules + * @return {string} + */ + function sanitizeWord (token, word, rules) { + // Empty string or doesn't need fixing. + if (!token.length || uncountables.hasOwnProperty(token)) { + return word; } - if (type$1.loadKind && type$1.loadKind !== 'scalar') { - throw new exception('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.'); - } + var len = rules.length; - if (type$1.multi) { - throw new exception('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.'); - } - }); + // Iterate over the sanitization rules and use the first one to match. + while (len--) { + var rule = rules[len]; - explicit.forEach(function (type$1) { - if (!(type$1 instanceof type)) { - throw new exception('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + if (rule[0].test(word)) return replace(word, rule); } - }); - var result = Object.create(Schema$1.prototype); + return word; + } - result.implicit = (this.implicit || []).concat(implicit); - result.explicit = (this.explicit || []).concat(explicit); + /** + * Replace a word with the updated word. + * + * @param {Object} replaceMap + * @param {Object} keepMap + * @param {Array} rules + * @return {Function} + */ + function replaceWord (replaceMap, keepMap, rules) { + return function (word) { + // Get the correct token and case restoration functions. + var token = word.toLowerCase(); - result.compiledImplicit = compileList(result, 'implicit'); - result.compiledExplicit = compileList(result, 'explicit'); - result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); + // Check against the keep object map. + if (keepMap.hasOwnProperty(token)) { + return restoreCase(word, token); + } - return result; -}; + // Check against the replacement map for a direct word replacement. + if (replaceMap.hasOwnProperty(token)) { + return restoreCase(word, replaceMap[token]); + } + // Run all the rules against the word. + return sanitizeWord(token, word, rules); + }; + } -var schema = Schema$1; + /** + * Check if a word is part of the map. + */ + function checkWord (replaceMap, keepMap, rules, bool) { + return function (word) { + var token = word.toLowerCase(); -var str = new type('tag:yaml.org,2002:str', { - kind: 'scalar', - construct: function (data) { return data !== null ? data : ''; } -}); + if (keepMap.hasOwnProperty(token)) return true; + if (replaceMap.hasOwnProperty(token)) return false; -var seq = new type('tag:yaml.org,2002:seq', { - kind: 'sequence', - construct: function (data) { return data !== null ? data : []; } -}); + return sanitizeWord(token, token, rules) === token; + }; + } -var map = new type('tag:yaml.org,2002:map', { - kind: 'mapping', - construct: function (data) { return data !== null ? data : {}; } -}); + /** + * Pluralize or singularize a word based on the passed in count. + * + * @param {string} word The word to pluralize + * @param {number} count How many of the word exist + * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks) + * @return {string} + */ + function pluralize (word, count, inclusive) { + var pluralized = count === 1 + ? pluralize.singular(word) : pluralize.plural(word); -var failsafe = new schema({ - explicit: [ - str, - seq, - map - ] -}); + return (inclusive ? count + ' ' : '') + pluralized; + } -function resolveYamlNull(data) { - if (data === null) return true; + /** + * Pluralize a word. + * + * @type {Function} + */ + pluralize.plural = replaceWord( + irregularSingles, irregularPlurals, pluralRules + ); - var max = data.length; + /** + * Check if a word is plural. + * + * @type {Function} + */ + pluralize.isPlural = checkWord( + irregularSingles, irregularPlurals, pluralRules + ); - return (max === 1 && data === '~') || - (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL')); -} + /** + * Singularize a word. + * + * @type {Function} + */ + pluralize.singular = replaceWord( + irregularPlurals, irregularSingles, singularRules + ); -function constructYamlNull() { - return null; -} - -function isNull(object) { - return object === null; -} - -var _null = new type('tag:yaml.org,2002:null', { - kind: 'scalar', - resolve: resolveYamlNull, - construct: constructYamlNull, - predicate: isNull, - represent: { - canonical: function () { return '~'; }, - lowercase: function () { return 'null'; }, - uppercase: function () { return 'NULL'; }, - camelcase: function () { return 'Null'; }, - empty: function () { return ''; } - }, - defaultStyle: 'lowercase' -}); - -function resolveYamlBoolean(data) { - if (data === null) return false; - - var max = data.length; - - return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) || - (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE')); -} - -function constructYamlBoolean(data) { - return data === 'true' || - data === 'True' || - data === 'TRUE'; -} - -function isBoolean(object) { - return Object.prototype.toString.call(object) === '[object Boolean]'; -} - -var bool = new type('tag:yaml.org,2002:bool', { - kind: 'scalar', - resolve: resolveYamlBoolean, - construct: constructYamlBoolean, - predicate: isBoolean, - represent: { - lowercase: function (object) { return object ? 'true' : 'false'; }, - uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; }, - camelcase: function (object) { return object ? 'True' : 'False'; } - }, - defaultStyle: 'lowercase' -}); - -function isHexCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) || - ((0x41/* A */ <= c) && (c <= 0x46/* F */)) || - ((0x61/* a */ <= c) && (c <= 0x66/* f */)); -} - -function isOctCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */)); -} - -function isDecCode(c) { - return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)); -} - -function resolveYamlInteger(data) { - if (data === null) return false; - - var max = data.length, - index = 0, - hasDigits = false, - ch; - - if (!max) return false; - - ch = data[index]; - - // sign - if (ch === '-' || ch === '+') { - ch = data[++index]; - } - - if (ch === '0') { - // 0 - if (index + 1 === max) return true; - ch = data[++index]; + /** + * Check if a word is singular. + * + * @type {Function} + */ + pluralize.isSingular = checkWord( + irregularPlurals, irregularSingles, singularRules + ); - // base 2, base 8, base 16 + /** + * Add a pluralization rule to the collection. + * + * @param {(string|RegExp)} rule + * @param {string} replacement + */ + pluralize.addPluralRule = function (rule, replacement) { + pluralRules.push([sanitizeRule(rule), replacement]); + }; - if (ch === 'b') { - // base 2 - index++; + /** + * Add a singularization rule to the collection. + * + * @param {(string|RegExp)} rule + * @param {string} replacement + */ + pluralize.addSingularRule = function (rule, replacement) { + singularRules.push([sanitizeRule(rule), replacement]); + }; - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (ch !== '0' && ch !== '1') return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; + /** + * Add an uncountable word rule. + * + * @param {(string|RegExp)} word + */ + pluralize.addUncountableRule = function (word) { + if (typeof word === 'string') { + uncountables[word.toLowerCase()] = true; + return; } + // Set singular and plural references for the word. + pluralize.addPluralRule(word, '$0'); + pluralize.addSingularRule(word, '$0'); + }; - if (ch === 'x') { - // base 16 - index++; + /** + * Add an irregular word definition. + * + * @param {string} single + * @param {string} plural + */ + pluralize.addIrregularRule = function (single, plural) { + plural = plural.toLowerCase(); + single = single.toLowerCase(); - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isHexCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; - } + irregularSingles[single] = plural; + irregularPlurals[plural] = single; + }; + /** + * Irregular rules. + */ + [ + // Pronouns. + ['I', 'we'], + ['me', 'us'], + ['he', 'they'], + ['she', 'they'], + ['them', 'them'], + ['myself', 'ourselves'], + ['yourself', 'yourselves'], + ['itself', 'themselves'], + ['herself', 'themselves'], + ['himself', 'themselves'], + ['themself', 'themselves'], + ['is', 'are'], + ['was', 'were'], + ['has', 'have'], + ['this', 'these'], + ['that', 'those'], + // Words ending in with a consonant and `o`. + ['echo', 'echoes'], + ['dingo', 'dingoes'], + ['volcano', 'volcanoes'], + ['tornado', 'tornadoes'], + ['torpedo', 'torpedoes'], + // Ends with `us`. + ['genus', 'genera'], + ['viscus', 'viscera'], + // Ends with `ma`. + ['stigma', 'stigmata'], + ['stoma', 'stomata'], + ['dogma', 'dogmata'], + ['lemma', 'lemmata'], + ['schema', 'schemata'], + ['anathema', 'anathemata'], + // Other irregular rules. + ['ox', 'oxen'], + ['axe', 'axes'], + ['die', 'dice'], + ['yes', 'yeses'], + ['foot', 'feet'], + ['eave', 'eaves'], + ['goose', 'geese'], + ['tooth', 'teeth'], + ['quiz', 'quizzes'], + ['human', 'humans'], + ['proof', 'proofs'], + ['carve', 'carves'], + ['valve', 'valves'], + ['looey', 'looies'], + ['thief', 'thieves'], + ['groove', 'grooves'], + ['pickaxe', 'pickaxes'], + ['passerby', 'passersby'] + ].forEach(function (rule) { + return pluralize.addIrregularRule(rule[0], rule[1]); + }); - if (ch === 'o') { - // base 8 - index++; + /** + * Pluralization rules. + */ + [ + [/s?$/i, 's'], + [/[^\u0000-\u007F]$/i, '$0'], + [/([^aeiou]ese)$/i, '$1'], + [/(ax|test)is$/i, '$1es'], + [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'], + [/(e[mn]u)s?$/i, '$1s'], + [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'], + [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'], + [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'], + [/(seraph|cherub)(?:im)?$/i, '$1im'], + [/(her|at|gr)o$/i, '$1oes'], + [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'], + [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'], + [/sis$/i, 'ses'], + [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'], + [/([^aeiouy]|qu)y$/i, '$1ies'], + [/([^ch][ieo][ln])ey$/i, '$1ies'], + [/(x|ch|ss|sh|zz)$/i, '$1es'], + [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'], + [/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'], + [/(pe)(?:rson|ople)$/i, '$1ople'], + [/(child)(?:ren)?$/i, '$1ren'], + [/eaux$/i, '$0'], + [/m[ae]n$/i, 'men'], + ['thou', 'you'] + ].forEach(function (rule) { + return pluralize.addPluralRule(rule[0], rule[1]); + }); - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isOctCode(data.charCodeAt(index))) return false; - hasDigits = true; - } - return hasDigits && ch !== '_'; - } - } - - // base 10 (except 0) + /** + * Singularization rules. + */ + [ + [/s$/i, ''], + [/(ss)$/i, '$1'], + [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'], + [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'], + [/ies$/i, 'y'], + [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'], + [/\b(mon|smil)ies$/i, '$1ey'], + [/\b((?:tit)?m|l)ice$/i, '$1ouse'], + [/(seraph|cherub)im$/i, '$1'], + [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'], + [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'], + [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'], + [/(test)(?:is|es)$/i, '$1is'], + [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'], + [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'], + [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'], + [/(alumn|alg|vertebr)ae$/i, '$1a'], + [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'], + [/(matr|append)ices$/i, '$1ix'], + [/(pe)(rson|ople)$/i, '$1rson'], + [/(child)ren$/i, '$1'], + [/(eau)x?$/i, '$1'], + [/men$/i, 'man'] + ].forEach(function (rule) { + return pluralize.addSingularRule(rule[0], rule[1]); + }); - // value should not start with `_`; - if (ch === '_') return false; + /** + * Uncountable rules. + */ + [ + // Singular words with no plurals. + 'adulthood', + 'advice', + 'agenda', + 'aid', + 'aircraft', + 'alcohol', + 'ammo', + 'analytics', + 'anime', + 'athletics', + 'audio', + 'bison', + 'blood', + 'bream', + 'buffalo', + 'butter', + 'carp', + 'cash', + 'chassis', + 'chess', + 'clothing', + 'cod', + 'commerce', + 'cooperation', + 'corps', + 'debris', + 'diabetes', + 'digestion', + 'elk', + 'energy', + 'equipment', + 'excretion', + 'expertise', + 'firmware', + 'flounder', + 'fun', + 'gallows', + 'garbage', + 'graffiti', + 'hardware', + 'headquarters', + 'health', + 'herpes', + 'highjinks', + 'homework', + 'housework', + 'information', + 'jeans', + 'justice', + 'kudos', + 'labour', + 'literature', + 'machinery', + 'mackerel', + 'mail', + 'media', + 'mews', + 'moose', + 'music', + 'mud', + 'manga', + 'news', + 'only', + 'personnel', + 'pike', + 'plankton', + 'pliers', + 'police', + 'pollution', + 'premises', + 'rain', + 'research', + 'rice', + 'salmon', + 'scissors', + 'series', + 'sewage', + 'shambles', + 'shrimp', + 'software', + 'species', + 'staff', + 'swine', + 'tennis', + 'traffic', + 'transportation', + 'trout', + 'tuna', + 'wealth', + 'welfare', + 'whiting', + 'wildebeest', + 'wildlife', + 'you', + /pok[eé]mon$/i, + // Regexes. + /[^aeiou]ese$/i, // "chinese", "japanese" + /deer$/i, // "deer", "reindeer" + /fish$/i, // "fish", "blowfish", "angelfish" + /measles$/i, + /o[iu]s$/i, // "carnivorous" + /pox$/i, // "chickpox", "smallpox" + /sheep$/i + ].forEach(pluralize.addUncountableRule); - for (; index < max; index++) { - ch = data[index]; - if (ch === '_') continue; - if (!isDecCode(data.charCodeAt(index))) { - return false; - } - hasDigits = true; - } + return pluralize; +}); +}(pluralize)); - // Should have digits and should not end with `_` - if (!hasDigits || ch === '_') return false; +var plural = pluralize.exports; - return true; -} +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module list-item-bullet-indent + * @fileoverview + * Warn when list item bullets are indented. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * removes all indentation before bullets. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * Paragraph. + * + * * List item + * * List item + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * Paragraph. + * + * ·* List item + * ·* List item + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 3:2: Incorrect indentation before bullet: remove 1 space + * 4:2: Incorrect indentation before bullet: remove 1 space + */ -function constructYamlInteger(data) { - var value = data, sign = 1, ch; +const remarkLintListItemBulletIndent = lintRule( + { + origin: 'remark-lint:list-item-bullet-indent', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-list-item-bullet-indent#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'list', (list, _, grandparent) => { + let index = -1; - if (value.indexOf('_') !== -1) { - value = value.replace(/_/g, ''); - } + while (++index < list.children.length) { + const item = list.children[index]; - ch = value[0]; + if ( + grandparent && + grandparent.type === 'root' && + grandparent.position && + typeof grandparent.position.start.column === 'number' && + item.position && + typeof item.position.start.column === 'number' + ) { + const indent = + item.position.start.column - grandparent.position.start.column; - if (ch === '-' || ch === '+') { - if (ch === '-') sign = -1; - value = value.slice(1); - ch = value[0]; + if (indent) { + file.message( + 'Incorrect indentation before bullet: remove ' + + indent + + ' ' + + plural('space', indent), + item.position.start + ); + } + } + } + }); } +); - if (value === '0') return 0; - - if (ch === '0') { - if (value[1] === 'b') return sign * parseInt(value.slice(2), 2); - if (value[1] === 'x') return sign * parseInt(value.slice(2), 16); - if (value[1] === 'o') return sign * parseInt(value.slice(2), 8); - } +var remarkLintListItemBulletIndent$1 = remarkLintListItemBulletIndent; - return sign * parseInt(value, 10); -} - -function isInteger(object) { - return (Object.prototype.toString.call(object)) === '[object Number]' && - (object % 1 === 0 && !common.isNegativeZero(object)); -} +/** + * @typedef {import('unist').Position} Position + * @typedef {import('unist').Point} Point + * + * @typedef {Partial} PointLike + * + * @typedef {Object} PositionLike + * @property {PointLike} [start] + * @property {PointLike} [end] + * + * @typedef {Object} NodeLike + * @property {PositionLike} [position] + */ -var int = new type('tag:yaml.org,2002:int', { - kind: 'scalar', - resolve: resolveYamlInteger, - construct: constructYamlInteger, - predicate: isInteger, - represent: { - binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); }, - octal: function (obj) { return obj >= 0 ? '0o' + obj.toString(8) : '-0o' + obj.toString(8).slice(1); }, - decimal: function (obj) { return obj.toString(10); }, - /* eslint-disable max-len */ - hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); } - }, - defaultStyle: 'decimal', - styleAliases: { - binary: [ 2, 'bin' ], - octal: [ 8, 'oct' ], - decimal: [ 10, 'dec' ], - hexadecimal: [ 16, 'hex' ] - } -}); +var pointStart = point('start'); +var pointEnd = point('end'); -var YAML_FLOAT_PATTERN = new RegExp( - // 2.5e4, 2.5 and integers - '^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' + - // .2e4, .2 - // special case, seems not from spec - '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' + - // .inf - '|[-+]?\\.(?:inf|Inf|INF)' + - // .nan - '|\\.(?:nan|NaN|NAN))$'); +/** + * Get the positional info of `node`. + * + * @param {'start'|'end'} type + */ +function point(type) { + return point -function resolveYamlFloat(data) { - if (data === null) return false; + /** + * Get the positional info of `node`. + * + * @param {NodeLike} [node] + * @returns {Point} + */ + function point(node) { + /** @type {Point} */ + // @ts-ignore looks like a point + var point = (node && node.position && node.position[type]) || {}; - if (!YAML_FLOAT_PATTERN.test(data) || - // Quick hack to not allow integers end with `_` - // Probably should update regexp & check speed - data[data.length - 1] === '_') { - return false; + return { + line: point.line || null, + column: point.column || null, + offset: point.offset > -1 ? point.offset : null + } } - - return true; } -function constructYamlFloat(data) { - var value, sign; - - value = data.replace(/_/g, '').toLowerCase(); - sign = value[0] === '-' ? -1 : 1; - - if ('+-'.indexOf(value[0]) >= 0) { - value = value.slice(1); - } - - if (value === '.inf') { - return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; +/** + * @typedef {Object} PointLike + * @property {number} [line] + * @property {number} [column] + * @property {number} [offset] + * + * @typedef {Object} PositionLike + * @property {PointLike} [start] + * @property {PointLike} [end] + * + * @typedef {Object} NodeLike + * @property {PositionLike} [position] + */ - } else if (value === '.nan') { - return NaN; - } - return sign * parseFloat(value, 10); +/** + * Check if `node` is *generated*. + * + * @param {NodeLike} [node] + * @returns {boolean} + */ +function generated(node) { + return ( + !node || + !node.position || + !node.position.start || + !node.position.start.line || + !node.position.start.column || + !node.position.end || + !node.position.end.line || + !node.position.end.column + ) } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module list-item-indent + * @fileoverview + * Warn when the spacing between a list item’s bullet and its content violates + * a given style. + * + * Options: `'tab-size'`, `'mixed'`, or `'space'`, default: `'tab-size'`. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * uses `'tab-size'` (named `'tab'` there) by default to ensure Markdown is + * seen the same way across vendors. + * This can be configured with the + * [`listItemIndent`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionslistitemindent) + * option. + * This rule’s `'space'` option is named `'1'` there. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * *···List + * ····item. + * + * Paragraph. + * + * 11.·List + * ····item. + * + * Paragraph. + * + * *···List + * ····item. + * + * *···List + * ····item. + * + * @example + * {"name": "ok.md", "setting": "mixed"} + * + * *·List item. + * + * Paragraph. + * + * 11.·List item + * + * Paragraph. + * + * *···List + * ····item. + * + * *···List + * ····item. + * + * @example + * {"name": "ok.md", "setting": "space"} + * + * *·List item. + * + * Paragraph. + * + * 11.·List item + * + * Paragraph. + * + * *·List + * ··item. + * + * *·List + * ··item. + * + * @example + * {"name": "not-ok.md", "setting": "space", "label": "input"} + * + * *···List + * ····item. + * + * @example + * {"name": "not-ok.md", "setting": "space", "label": "output"} + * + * 1:5: Incorrect list-item indent: remove 2 spaces + * + * @example + * {"name": "not-ok.md", "setting": "tab-size", "label": "input"} + * + * *·List + * ··item. + * + * @example + * {"name": "not-ok.md", "setting": "tab-size", "label": "output"} + * + * 1:3: Incorrect list-item indent: add 2 spaces + * + * @example + * {"name": "not-ok.md", "setting": "mixed", "label": "input"} + * + * *···List item. + * + * @example + * {"name": "not-ok.md", "setting": "mixed", "label": "output"} + * + * 1:5: Incorrect list-item indent: remove 2 spaces + * + * @example + * {"name": "not-ok.md", "setting": "💩", "label": "output", "positionless": true} + * + * 1:1: Incorrect list-item indent style `💩`: use either `'tab-size'`, `'space'`, or `'mixed'` + */ -var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; - -function representYamlFloat(object, style) { - var res; +const remarkLintListItemIndent = lintRule( + { + origin: 'remark-lint:list-item-indent', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-list-item-indent#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'tab-size') => { + const value = String(file); - if (isNaN(object)) { - switch (style) { - case 'lowercase': return '.nan'; - case 'uppercase': return '.NAN'; - case 'camelcase': return '.NaN'; - } - } else if (Number.POSITIVE_INFINITY === object) { - switch (style) { - case 'lowercase': return '.inf'; - case 'uppercase': return '.INF'; - case 'camelcase': return '.Inf'; - } - } else if (Number.NEGATIVE_INFINITY === object) { - switch (style) { - case 'lowercase': return '-.inf'; - case 'uppercase': return '-.INF'; - case 'camelcase': return '-.Inf'; + if (option !== 'tab-size' && option !== 'space' && option !== 'mixed') { + file.fail( + 'Incorrect list-item indent style `' + + option + + "`: use either `'tab-size'`, `'space'`, or `'mixed'`" + ); } - } else if (common.isNegativeZero(object)) { - return '-0.0'; - } - - res = object.toString(10); - // JS stringifier can build scientific format without dots: 5e-100, - // while YAML requres dot: 5.e-100. Fix it with simple hack + visit$1(tree, 'list', (node) => { + if (generated(node)) return - return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res; -} + const spread = node.spread; + let index = -1; -function isFloat(object) { - return (Object.prototype.toString.call(object) === '[object Number]') && - (object % 1 !== 0 || common.isNegativeZero(object)); -} + while (++index < node.children.length) { + const item = node.children[index]; + const head = item.children[0]; + const final = pointStart(head); -var float = new type('tag:yaml.org,2002:float', { - kind: 'scalar', - resolve: resolveYamlFloat, - construct: constructYamlFloat, - predicate: isFloat, - represent: representYamlFloat, - defaultStyle: 'lowercase' -}); + const marker = value + .slice(pointStart(item).offset, final.offset) + .replace(/\[[x ]?]\s*$/i, ''); -var json = failsafe.extend({ - implicit: [ - _null, - bool, - int, - float - ] -}); + const bulletSize = marker.replace(/\s+$/, '').length; -var core = json; + const style = + option === 'tab-size' || (option === 'mixed' && spread) + ? Math.ceil(bulletSize / 4) * 4 + : bulletSize + 1; -var YAML_DATE_REGEXP = new RegExp( - '^([0-9][0-9][0-9][0-9])' + // [1] year - '-([0-9][0-9])' + // [2] month - '-([0-9][0-9])$'); // [3] day + if (marker.length !== style) { + const diff = style - marker.length; + const abs = Math.abs(diff); -var YAML_TIMESTAMP_REGEXP = new RegExp( - '^([0-9][0-9][0-9][0-9])' + // [1] year - '-([0-9][0-9]?)' + // [2] month - '-([0-9][0-9]?)' + // [3] day - '(?:[Tt]|[ \\t]+)' + // ... - '([0-9][0-9]?)' + // [4] hour - ':([0-9][0-9])' + // [5] minute - ':([0-9][0-9])' + // [6] second - '(?:\\.([0-9]*))?' + // [7] fraction - '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour - '(?::([0-9][0-9]))?))?$'); // [11] tz_minute + file.message( + 'Incorrect list-item indent: ' + + (diff > 0 ? 'add' : 'remove') + + ' ' + + abs + + ' ' + + plural('space', abs), + final + ); + } + } + }); + } +); -function resolveYamlTimestamp(data) { - if (data === null) return false; - if (YAML_DATE_REGEXP.exec(data) !== null) return true; - if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; - return false; -} +var remarkLintListItemIndent$1 = remarkLintListItemIndent; -function constructYamlTimestamp(data) { - var match, year, month, day, hour, minute, second, fraction = 0, - delta = null, tz_hour, tz_minute, date; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-auto-link-without-protocol + * @fileoverview + * Warn for autolinks without protocol. + * Autolinks are URLs enclosed in `<` (less than) and `>` (greater than) + * characters. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * adds a protocol where needed. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * + * + * + * Most Markdown vendors don’t recognize the following as a link: + * + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:14: All automatic links must start with a protocol + */ - match = YAML_DATE_REGEXP.exec(data); - if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); +// Protocol expression. +// See: . +const protocol = /^[a-z][a-z+.-]+:\/?/i; - if (match === null) throw new Error('Date resolve error'); +const remarkLintNoAutoLinkWithoutProtocol = lintRule( + { + origin: 'remark-lint:no-auto-link-without-protocol', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-auto-link-without-protocol#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'link', (node) => { + if ( + !generated(node) && + pointStart(node).column === pointStart(node.children[0]).column - 1 && + pointEnd(node).column === + pointEnd(node.children[node.children.length - 1]).column + 1 && + !protocol.test(toString(node)) + ) { + file.message('All automatic links must start with a protocol', node); + } + }); + } +); - // match: [1] year [2] month [3] day +var remarkLintNoAutoLinkWithoutProtocol$1 = remarkLintNoAutoLinkWithoutProtocol; - year = +(match[1]); - month = +(match[2]) - 1; // JS month starts with 0 - day = +(match[3]); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-blockquote-without-marker + * @fileoverview + * Warn when blank lines without `>` (greater than) markers are found in a + * block quote. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * adds markers to every line in a block quote. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * > Foo… + * > …bar… + * > …baz. + * + * @example + * {"name": "ok-tabs.md"} + * + * >»Foo… + * >»…bar… + * >»…baz. + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * > Foo… + * …bar… + * > …baz. + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 2:1: Missing marker in block quote + * + * @example + * {"name": "not-ok-tabs.md", "label": "input"} + * + * >»Foo… + * »…bar… + * …baz. + * + * @example + * {"name": "not-ok-tabs.md", "label": "output"} + * + * 2:1: Missing marker in block quote + * 3:1: Missing marker in block quote + */ - if (!match[4]) { // no hour - return new Date(Date.UTC(year, month, day)); - } +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' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + const value = String(file); + const loc = location(file); - // match: [4] hour [5] minute [6] second [7] fraction + visit$1(tree, 'blockquote', (node) => { + let index = -1; - hour = +(match[4]); - minute = +(match[5]); - second = +(match[6]); + while (++index < node.children.length) { + const child = node.children[index]; - if (match[7]) { - fraction = match[7].slice(0, 3); - while (fraction.length < 3) { // milli-seconds - fraction += '0'; - } - fraction = +fraction; - } + if (child.type === 'paragraph' && !generated(child)) { + const end = pointEnd(child).line; + const column = pointStart(child).column; + let line = pointStart(child).line; - // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute + // Skip past the first line. + while (++line <= end) { + const offset = loc.toOffset({line, column}); - if (match[9]) { - tz_hour = +(match[10]); - tz_minute = +(match[11] || 0); - delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds - if (match[9] === '-') delta = -delta; + if (/>[\t ]+$/.test(value.slice(offset - 5, offset))) { + continue + } + + // Roughly here. + file.message('Missing marker in block quote', { + line, + column: column - 2 + }); + } + } + } + }); } +); - date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); +var remarkLintNoBlockquoteWithoutMarker$1 = remarkLintNoBlockquoteWithoutMarker; - if (delta) date.setTime(date.getTime() - delta); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-literal-urls + * @fileoverview + * Warn for literal URLs in text. + * URLs are treated as links in some Markdown vendors, but not in others. + * To make sure they are always linked, wrap them in `<` (less than) and `>` + * (greater than). + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * never creates literal URLs and always uses `<` (less than) and `>` + * (greater than). + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * + * + * @example + * {"name": "not-ok.md", "label": "input", "gfm": true} + * + * http://foo.bar/baz + * + * @example + * {"name": "not-ok.md", "label": "output", "gfm": true} + * + * 1:1-1:19: Don’t use literal URLs without angle brackets + */ - return date; -} +const remarkLintNoLiteralUrls = lintRule( + { + origin: 'remark-lint:no-literal-urls', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-literal-urls#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'link', (node) => { + const value = toString(node); -function representYamlTimestamp(object /*, style*/) { - return object.toISOString(); -} + if ( + !generated(node) && + pointStart(node).column === pointStart(node.children[0]).column && + pointEnd(node).column === + pointEnd(node.children[node.children.length - 1]).column && + (node.url === 'mailto:' + value || node.url === value) + ) { + file.message('Don’t use literal URLs without angle brackets', node); + } + }); + } +); -var timestamp = new type('tag:yaml.org,2002:timestamp', { - kind: 'scalar', - resolve: resolveYamlTimestamp, - construct: constructYamlTimestamp, - instanceOf: Date, - represent: representYamlTimestamp -}); +var remarkLintNoLiteralUrls$1 = remarkLintNoLiteralUrls; -function resolveYamlMerge(data) { - return data === '<<' || data === null; -} +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module ordered-list-marker-style + * @fileoverview + * Warn when the list item marker style of ordered lists violate a given style. + * + * Options: `'consistent'`, `'.'`, or `')'`, default: `'consistent'`. + * + * `'consistent'` detects the first used list style and warns when subsequent + * lists use different styles. + * + * @example + * {"name": "ok.md"} + * + * 1. Foo + * + * + * 1. Bar + * + * Unordered lists are not affected by this rule. + * + * * Foo + * + * @example + * {"name": "ok.md", "setting": "."} + * + * 1. Foo + * + * 2. Bar + * + * @example + * {"name": "ok.md", "setting": ")"} + * + * 1) Foo + * + * 2) Bar + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * 1. Foo + * + * 2) Bar + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 3:1-3:8: Marker style should be `.` + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} + * + * 1:1: Incorrect ordered list item marker style `💩`: use either `'.'` or `')'` + */ -var merge = new type('tag:yaml.org,2002:merge', { - kind: 'scalar', - resolve: resolveYamlMerge -}); +const remarkLintOrderedListMarkerStyle = lintRule( + { + origin: 'remark-lint:ordered-list-marker-style', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-ordered-list-marker-style#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); -/*eslint-disable no-bitwise*/ + if (option !== 'consistent' && option !== '.' && option !== ')') { + file.fail( + 'Incorrect ordered list item marker style `' + + option + + "`: use either `'.'` or `')'`" + ); + } + visit$1(tree, 'list', (node) => { + let index = -1; + if (!node.ordered) return + while (++index < node.children.length) { + const child = node.children[index]; + if (!generated(child)) { + const marker = /** @type {Marker} */ ( + value + .slice( + pointStart(child).offset, + pointStart(child.children[0]).offset + ) + .replace(/\s|\d/g, '') + .replace(/\[[x ]?]\s*$/i, '') + ); -// [ 64, 65, 66 ] -> [ padding, CR, LF ] -var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r'; + if (option === 'consistent') { + option = marker; + } else if (marker !== option) { + file.message('Marker style should be `' + option + '`', child); + } + } + } + }); + } +); +var remarkLintOrderedListMarkerStyle$1 = remarkLintOrderedListMarkerStyle; -function resolveYamlBinary(data) { - if (data === null) return false; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module hard-break-spaces + * @fileoverview + * Warn when too many spaces are used to create a hard break. + * + * @example + * {"name": "ok.md"} + * + * Lorem ipsum·· + * dolor sit amet + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * Lorem ipsum··· + * dolor sit amet. + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:12-2:1: Use two spaces for hard line breaks + */ - var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP; +const remarkLintHardBreakSpaces = lintRule( + { + origin: 'remark-lint:hard-break-spaces', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-hard-break-spaces#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + const value = String(file); - // Convert one by one. - for (idx = 0; idx < max; idx++) { - code = map.indexOf(data.charAt(idx)); + visit$1(tree, 'break', (node) => { + if (!generated(node)) { + const slice = value + .slice(pointStart(node).offset, pointEnd(node).offset) + .split('\n', 1)[0] + .replace(/\r$/, ''); - // Skip CR/LF - if (code > 64) continue; - - // Fail on illegal characters - if (code < 0) return false; - - bitlen += 6; + if (slice.length > 2) { + file.message('Use two spaces for hard line breaks', node); + } + } + }); } +); - // If there are any bits left, source was corrupted - return (bitlen % 8) === 0; -} - -function constructYamlBinary(data) { - var idx, tailbits, - input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan - max = input.length, - map = BASE64_MAP, - bits = 0, - result = []; - - // Collect by 6*4 bits (3 bytes) +var remarkLintHardBreakSpaces$1 = remarkLintHardBreakSpaces; - for (idx = 0; idx < max; idx++) { - if ((idx % 4 === 0) && idx) { - result.push((bits >> 16) & 0xFF); - result.push((bits >> 8) & 0xFF); - result.push(bits & 0xFF); - } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-duplicate-definitions + * @fileoverview + * Warn when duplicate definitions are found. + * + * @example + * {"name": "ok.md"} + * + * [foo]: bar + * [baz]: qux + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * [foo]: bar + * [foo]: qux + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 2:1-2:11: Do not use definitions with the same identifier (1:1) + */ - bits = (bits << 6) | map.indexOf(input.charAt(idx)); - } +const remarkLintNoDuplicateDefinitions = lintRule( + { + origin: 'remark-lint:no-duplicate-definitions', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-duplicate-definitions#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + /** @type {Record} */ + const map = Object.create(null); - // Dump tail + visit$1(tree, (node) => { + if ( + (node.type === 'definition' || node.type === 'footnoteDefinition') && + !generated(node) + ) { + const identifier = node.identifier; + const duplicate = map[identifier]; - tailbits = (max % 4) * 6; + if (duplicate) { + file.message( + 'Do not use definitions with the same identifier (' + + duplicate + + ')', + node + ); + } - if (tailbits === 0) { - result.push((bits >> 16) & 0xFF); - result.push((bits >> 8) & 0xFF); - result.push(bits & 0xFF); - } else if (tailbits === 18) { - result.push((bits >> 10) & 0xFF); - result.push((bits >> 2) & 0xFF); - } else if (tailbits === 12) { - result.push((bits >> 4) & 0xFF); + map[identifier] = stringifyPosition(pointStart(node)); + } + }); } +); - return new Uint8Array(result); -} +var remarkLintNoDuplicateDefinitions$1 = remarkLintNoDuplicateDefinitions; -function representYamlBinary(object /*, style*/) { - var result = '', bits = 0, idx, tail, - max = object.length, - map = BASE64_MAP; +/** + * @typedef {import('mdast').Heading} Heading + * @typedef {'atx'|'atx-closed'|'setext'} Style + */ - // Convert every three bytes to 4 ASCII characters. +/** + * @param {Heading} node + * @param {Style} [relative] + * @returns {Style|null} + */ +function headingStyle(node, relative) { + var last = node.children[node.children.length - 1]; + var depth = node.depth; + var pos = node && node.position && node.position.end; + var final = last && last.position && last.position.end; - for (idx = 0; idx < max; idx++) { - if ((idx % 3 === 0) && idx) { - result += map[(bits >> 18) & 0x3F]; - result += map[(bits >> 12) & 0x3F]; - result += map[(bits >> 6) & 0x3F]; - result += map[bits & 0x3F]; + if (!pos) { + return null + } + + // This can only occur for `'atx'` and `'atx-closed'` headings. + // This might incorrectly match `'atx'` headings with lots of trailing white + // space as an `'atx-closed'` heading. + if (!last) { + if (pos.column - 1 <= depth * 2) { + return consolidate(depth, relative) } - bits = (bits << 8) + object[idx]; + return 'atx-closed' } - // Dump tail - - tail = max % 3; + if (final.line + 1 === pos.line) { + return 'setext' + } - if (tail === 0) { - result += map[(bits >> 18) & 0x3F]; - result += map[(bits >> 12) & 0x3F]; - result += map[(bits >> 6) & 0x3F]; - result += map[bits & 0x3F]; - } else if (tail === 2) { - result += map[(bits >> 10) & 0x3F]; - result += map[(bits >> 4) & 0x3F]; - result += map[(bits << 2) & 0x3F]; - result += map[64]; - } else if (tail === 1) { - result += map[(bits >> 2) & 0x3F]; - result += map[(bits << 4) & 0x3F]; - result += map[64]; - result += map[64]; + if (final.column + depth < pos.column) { + return 'atx-closed' } - return result; + return consolidate(depth, relative) } -function isBinary(obj) { - return Object.prototype.toString.call(obj) === '[object Uint8Array]'; +/** + * Get the probable style of an atx-heading, depending on preferred style. + * + * @param {number} depth + * @param {Style} relative + * @returns {Style|null} + */ +function consolidate(depth, relative) { + return depth < 3 + ? 'atx' + : relative === 'atx' || relative === 'setext' + ? relative + : null } -var binary = new type('tag:yaml.org,2002:binary', { - kind: 'scalar', - resolve: resolveYamlBinary, - construct: constructYamlBinary, - predicate: isBinary, - represent: representYamlBinary -}); - -var _hasOwnProperty$3 = Object.prototype.hasOwnProperty; -var _toString$2 = Object.prototype.toString; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-heading-content-indent + * @fileoverview + * Warn when content of headings is indented. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * removes all unneeded padding around content in headings. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * #·Foo + * + * ## Bar·## + * + * ##·Baz + * + * Setext headings are not affected. + * + * Baz + * === + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * #··Foo + * + * ## Bar··## + * + * ##··Baz + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:4: Remove 1 space before this heading’s content + * 3:7: Remove 1 space after this heading’s content + * 5:7: Remove 1 space before this heading’s content + * + * @example + * {"name": "empty-heading.md"} + * + * #·· + */ -function resolveYamlOmap(data) { - if (data === null) return true; +const remarkLintNoHeadingContentIndent = lintRule( + { + origin: 'remark-lint:no-heading-content-indent', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-heading-content-indent#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'heading', (node) => { + if (generated(node)) { + return + } - var objectKeys = [], index, length, pair, pairKey, pairHasKey, - object = data; + const type = headingStyle(node, 'atx'); - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; - pairHasKey = false; + if (type === 'atx' || type === 'atx-closed') { + const head = pointStart(node.children[0]).column; - if (_toString$2.call(pair) !== '[object Object]') return false; + // Ignore empty headings. + if (!head) { + return + } - for (pairKey in pair) { - if (_hasOwnProperty$3.call(pair, pairKey)) { - if (!pairHasKey) pairHasKey = true; - else return false; + const diff = head - pointStart(node).column - 1 - node.depth; + + if (diff) { + file.message( + 'Remove ' + + Math.abs(diff) + + ' ' + + plural('space', Math.abs(diff)) + + ' before this heading’s content', + pointStart(node.children[0]) + ); + } } - } - if (!pairHasKey) return false; + // Closed ATX headings always must have a space between their content and + // the final hashes, thus, there is no `add x spaces`. + if (type === 'atx-closed') { + const final = pointEnd(node.children[node.children.length - 1]); + const diff = pointEnd(node).column - final.column - 1 - node.depth; - if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); - else return false; + if (diff) { + file.message( + 'Remove ' + + diff + + ' ' + + plural('space', diff) + + ' after this heading’s content', + final + ); + } + } + }); } +); - return true; -} - -function constructYamlOmap(data) { - return data !== null ? data : []; -} - -var omap = new type('tag:yaml.org,2002:omap', { - kind: 'sequence', - resolve: resolveYamlOmap, - construct: constructYamlOmap -}); - -var _toString$1 = Object.prototype.toString; - -function resolveYamlPairs(data) { - if (data === null) return true; - - var index, length, pair, keys, result, - object = data; - - result = new Array(object.length); - - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; - - if (_toString$1.call(pair) !== '[object Object]') return false; +var remarkLintNoHeadingContentIndent$1 = remarkLintNoHeadingContentIndent; - keys = Object.keys(pair); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-inline-padding + * @fileoverview + * Warn when phrasing content is padded with spaces between their markers and + * content. + * + * Warns for emphasis, strong, delete, image, and link. + * + * @example + * {"name": "ok.md"} + * + * Alpha [bravo](http://echo.fox/trot) + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * Alpha [ bravo ](http://echo.fox/trot) + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:7-1:38: Don’t pad `link` with inner spaces + */ - if (keys.length !== 1) return false; +const remarkLintNoInlinePadding = lintRule( + { + origin: 'remark-lint:no-inline-padding', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-inline-padding#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + // Note: `emphasis`, `strong`, `delete` (GFM) can’t have padding anymore + // since CM. + visit$1(tree, (node) => { + if ( + (node.type === 'link' || node.type === 'linkReference') && + !generated(node) + ) { + const value = toString(node); - result[index] = [ keys[0], pair[keys[0]] ]; + if (value.charAt(0) === ' ' || value.charAt(value.length - 1) === ' ') { + file.message('Don’t pad `' + node.type + '` with inner spaces', node); + } + } + }); } +); - return true; -} - -function constructYamlPairs(data) { - if (data === null) return []; - - var index, length, pair, keys, result, - object = data; - - result = new Array(object.length); - - for (index = 0, length = object.length; index < length; index += 1) { - pair = object[index]; +var remarkLintNoInlinePadding$1 = remarkLintNoInlinePadding; - keys = Object.keys(pair); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-shortcut-reference-image + * @fileoverview + * Warn when shortcut reference images are used. + * + * Shortcut references render as images when a definition is found, and as + * plain text without definition. + * Sometimes, you don’t intend to create an image from the reference, but this + * rule still warns anyway. + * In that case, you can escape the reference like so: `!\[foo]`. + * + * @example + * {"name": "ok.md"} + * + * ![foo][] + * + * [foo]: http://foo.bar/baz.png + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * ![foo] + * + * [foo]: http://foo.bar/baz.png + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:7: Use the trailing [] on reference images + */ - result[index] = [ keys[0], pair[keys[0]] ]; +const remarkLintNoShortcutReferenceImage = lintRule( + { + origin: 'remark-lint:no-shortcut-reference-image', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-shortcut-reference-image#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'imageReference', (node) => { + if (!generated(node) && node.referenceType === 'shortcut') { + file.message('Use the trailing [] on reference images', node); + } + }); } +); - return result; -} - -var pairs = new type('tag:yaml.org,2002:pairs', { - kind: 'sequence', - resolve: resolveYamlPairs, - construct: constructYamlPairs -}); - -var _hasOwnProperty$2 = Object.prototype.hasOwnProperty; - -function resolveYamlSet(data) { - if (data === null) return true; +var remarkLintNoShortcutReferenceImage$1 = remarkLintNoShortcutReferenceImage; - var key, object = data; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module no-shortcut-reference-link + * @fileoverview + * Warn when shortcut reference links are used. + * + * Shortcut references render as links when a definition is found, and as + * plain text without definition. + * Sometimes, you don’t intend to create a link from the reference, but this + * rule still warns anyway. + * In that case, you can escape the reference like so: `\[foo]`. + * + * @example + * {"name": "ok.md"} + * + * [foo][] + * + * [foo]: http://foo.bar/baz + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * [foo] + * + * [foo]: http://foo.bar/baz + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:6: Use the trailing `[]` on reference links + */ - for (key in object) { - if (_hasOwnProperty$2.call(object, key)) { - if (object[key] !== null) return false; - } +const remarkLintNoShortcutReferenceLink = lintRule( + { + origin: 'remark-lint:no-shortcut-reference-link', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-shortcut-reference-link#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + visit$1(tree, 'linkReference', (node) => { + if (!generated(node) && node.referenceType === 'shortcut') { + file.message('Use the trailing `[]` on reference links', node); + } + }); } +); - return true; -} +var remarkLintNoShortcutReferenceLink$1 = remarkLintNoShortcutReferenceLink; -function constructYamlSet(data) { - return data !== null ? data : {}; -} - -var set = new type('tag:yaml.org,2002:set', { - kind: 'mapping', - resolve: resolveYamlSet, - construct: constructYamlSet -}); - -var _default = core.extend({ - implicit: [ - timestamp, - merge - ], - explicit: [ - binary, - omap, - pairs, - set - ] -}); +/** + * @author Titus Wormer + * @copyright 2016 Titus Wormer + * @license MIT + * @module no-undefined-references + * @fileoverview + * Warn when references to undefined definitions are found. + * + * Options: `Object`, optional. + * + * The object can have an `allow` field, set to an array of strings that may + * appear between `[` and `]`, but that should not be treated as link + * identifiers. + * + * @example + * {"name": "ok.md"} + * + * [foo][] + * + * Just a [ bracket. + * + * Typically, you’d want to use escapes (with a backslash: \\) to escape what + * could turn into a \[reference otherwise]. + * + * Just two braces can’t link: []. + * + * [foo]: https://example.com + * + * @example + * {"name": "ok-allow.md", "setting": {"allow": ["...", "…"]}} + * + * > Eliding a portion of a quoted passage […] is acceptable. + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * [bar] + * + * [baz][] + * + * [text][qux] + * + * Spread [over + * lines][] + * + * > in [a + * > block quote][] + * + * [asd][a + * + * Can include [*emphasis*]. + * + * Multiple pairs: [a][b][c]. + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:6: Found reference to undefined definition + * 3:1-3:8: Found reference to undefined definition + * 5:1-5:12: Found reference to undefined definition + * 7:8-8:9: Found reference to undefined definition + * 10:6-11:17: Found reference to undefined definition + * 13:1-13:6: Found reference to undefined definition + * 15:13-15:25: Found reference to undefined definition + * 17:17-17:23: Found reference to undefined definition + * 17:23-17:26: Found reference to undefined definition + */ -/*eslint-disable max-len,no-use-before-define*/ +const remarkLintNoUndefinedReferences = lintRule( + { + origin: 'remark-lint:no-undefined-references', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-undefined-references#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = {}) => { + const contents = String(file); + const loc = location(file); + const lineEnding = /(\r?\n|\r)[\t ]*(>[\t ]*)*/g; + const allow = new Set( + (option.allow || []).map((d) => normalizeIdentifier(d)) + ); + /** @type {Record} */ + const map = Object.create(null); + visit$1(tree, (node) => { + if ( + (node.type === 'definition' || node.type === 'footnoteDefinition') && + !generated(node) + ) { + map[normalizeIdentifier(node.identifier)] = true; + } + }); + visit$1(tree, (node) => { + // CM specifiers that references only form when defined. + // Still, they could be added by plugins, so let’s keep it. + /* c8 ignore next 10 */ + if ( + (node.type === 'imageReference' || + node.type === 'linkReference' || + node.type === 'footnoteReference') && + !generated(node) && + !(normalizeIdentifier(node.identifier) in map) && + !allow.has(normalizeIdentifier(node.identifier)) + ) { + file.message('Found reference to undefined definition', node); + } + if (node.type === 'paragraph' || node.type === 'heading') { + findInPhrasing(node); + } + }); + /** + * @param {Heading|Paragraph} node + */ + function findInPhrasing(node) { + /** @type {Range[]} */ + let ranges = []; + visit$1(node, (child) => { + // Ignore the node itself. + if (child === node) return + // Can’t have links in links, so reset ranges. + if (child.type === 'link' || child.type === 'linkReference') { + ranges = []; + return SKIP$1 + } -var _hasOwnProperty$1 = Object.prototype.hasOwnProperty; + // Enter non-text. + if (child.type !== 'text') return + const start = pointStart(child).offset; + const end = pointEnd(child).offset; -var CONTEXT_FLOW_IN = 1; -var CONTEXT_FLOW_OUT = 2; -var CONTEXT_BLOCK_IN = 3; -var CONTEXT_BLOCK_OUT = 4; + // Bail if there’s no positional info. + if (typeof start !== 'number' || typeof end !== 'number') { + return EXIT$1 + } + const source = contents.slice(start, end); + /** @type {Array.<[number, string]>} */ + const lines = [[start, '']]; + let last = 0; -var CHOMPING_CLIP = 1; -var CHOMPING_STRIP = 2; -var CHOMPING_KEEP = 3; + lineEnding.lastIndex = 0; + let match = lineEnding.exec(source); + while (match) { + const index = match.index; + lines[lines.length - 1][1] = source.slice(last, index); + last = index + match[0].length; + lines.push([start + last, '']); + match = lineEnding.exec(source); + } -var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; -var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; -var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; -var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; -var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; + lines[lines.length - 1][1] = source.slice(last); + let lineIndex = -1; + while (++lineIndex < lines.length) { + const line = lines[lineIndex][1]; + let index = 0; -function _class(obj) { return Object.prototype.toString.call(obj); } + while (index < line.length) { + const code = line.charCodeAt(index); -function is_EOL(c) { - return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); -} + // Skip past escaped brackets. + if (code === 92) { + const next = line.charCodeAt(index + 1); + index++; -function is_WHITE_SPACE(c) { - return (c === 0x09/* Tab */) || (c === 0x20/* Space */); -} + if (next === 91 || next === 93) { + index++; + } + } + // Opening bracket. + else if (code === 91) { + ranges.push([lines[lineIndex][0] + index]); + index++; + } + // Close bracket. + else if (code === 93) { + // No opening. + if (ranges.length === 0) { + index++; + } else if (line.charCodeAt(index + 1) === 91) { + index++; -function is_WS_OR_EOL(c) { - return (c === 0x09/* Tab */) || - (c === 0x20/* Space */) || - (c === 0x0A/* LF */) || - (c === 0x0D/* CR */); -} + // Collapsed or full. + let range = ranges.pop(); -function is_FLOW_INDICATOR(c) { - return c === 0x2C/* , */ || - c === 0x5B/* [ */ || - c === 0x5D/* ] */ || - c === 0x7B/* { */ || - c === 0x7D/* } */; -} + // Range should always exist. + // eslint-disable-next-line max-depth + if (range) { + range.push(lines[lineIndex][0] + index); -function fromHexCode(c) { - var lc; + // This is the end of a reference already. + // eslint-disable-next-line max-depth + if (range.length === 4) { + handleRange(range); + range = []; + } - if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { - return c - 0x30; - } + range.push(lines[lineIndex][0] + index); + ranges.push(range); + index++; + } + } else { + index++; - /*eslint-disable no-bitwise*/ - lc = c | 0x20; + // Shortcut or typical end of a reference. + const range = ranges.pop(); - if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) { - return lc - 0x61 + 10; - } - - return -1; -} - -function escapedHexLen(c) { - if (c === 0x78/* x */) { return 2; } - if (c === 0x75/* u */) { return 4; } - if (c === 0x55/* U */) { return 8; } - return 0; -} - -function fromDecimalCode(c) { - if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { - return c - 0x30; - } - - return -1; -} - -function simpleEscapeSequence(c) { - /* eslint-disable indent */ - return (c === 0x30/* 0 */) ? '\x00' : - (c === 0x61/* a */) ? '\x07' : - (c === 0x62/* b */) ? '\x08' : - (c === 0x74/* t */) ? '\x09' : - (c === 0x09/* Tab */) ? '\x09' : - (c === 0x6E/* n */) ? '\x0A' : - (c === 0x76/* v */) ? '\x0B' : - (c === 0x66/* f */) ? '\x0C' : - (c === 0x72/* r */) ? '\x0D' : - (c === 0x65/* e */) ? '\x1B' : - (c === 0x20/* Space */) ? ' ' : - (c === 0x22/* " */) ? '\x22' : - (c === 0x2F/* / */) ? '/' : - (c === 0x5C/* \ */) ? '\x5C' : - (c === 0x4E/* N */) ? '\x85' : - (c === 0x5F/* _ */) ? '\xA0' : - (c === 0x4C/* L */) ? '\u2028' : - (c === 0x50/* P */) ? '\u2029' : ''; -} - -function charFromCodepoint(c) { - if (c <= 0xFFFF) { - return String.fromCharCode(c); - } - // Encode UTF-16 surrogate pair - // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF - return String.fromCharCode( - ((c - 0x010000) >> 10) + 0xD800, - ((c - 0x010000) & 0x03FF) + 0xDC00 - ); -} + // Range should always exist. + // eslint-disable-next-line max-depth + if (range) { + range.push(lines[lineIndex][0] + index); + handleRange(range); + } + } + } + // Anything else. + else { + index++; + } + } + } + }); -var simpleEscapeCheck = new Array(256); // integer, for fast access -var simpleEscapeMap = new Array(256); -for (var i = 0; i < 256; i++) { - simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; - simpleEscapeMap[i] = simpleEscapeSequence(i); -} + let index = -1; + while (++index < ranges.length) { + handleRange(ranges[index]); + } -function State$1(input, options) { - this.input = input; + return SKIP$1 - this.filename = options['filename'] || null; - this.schema = options['schema'] || _default; - this.onWarning = options['onWarning'] || null; - // (Hidden) Remove? makes the loader to expect YAML 1.1 documents - // if such documents have no explicit %YAML directive - this.legacy = options['legacy'] || false; + /** + * @param {Range} range + */ + function handleRange(range) { + if (range.length === 1) return + if (range.length === 3) range.length = 2; - this.json = options['json'] || false; - this.listener = options['listener'] || null; + // No need to warn for just `[]`. + if (range.length === 2 && range[0] + 2 === range[1]) return - this.implicitTypes = this.schema.compiledImplicit; - this.typeMap = this.schema.compiledTypeMap; + const offset = range.length === 4 && range[2] + 2 !== range[3] ? 2 : 0; + const id = contents + .slice(range[0 + offset] + 1, range[1 + offset] - 1) + .replace(lineEnding, ' '); + const pos = { + start: loc.toPoint(range[0]), + end: loc.toPoint(range[range.length - 1]) + }; - this.length = input.length; - this.position = 0; - this.line = 0; - this.lineStart = 0; - this.lineIndent = 0; + if ( + !generated({position: pos}) && + !(normalizeIdentifier(id) in map) && + !allow.has(normalizeIdentifier(id)) + ) { + file.message('Found reference to undefined definition', pos); + } + } + } + } +); - // position of first leading tab in the current line, - // used to make sure there are no tabs in the indentation - this.firstTabInLine = -1; +var remarkLintNoUndefinedReferences$1 = remarkLintNoUndefinedReferences; - this.documents = []; +/** + * @author Titus Wormer + * @copyright 2016 Titus Wormer + * @license MIT + * @module no-unused-definitions + * @fileoverview + * Warn when unused definitions are found. + * + * @example + * {"name": "ok.md"} + * + * [foo][] + * + * [foo]: https://example.com + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * [bar]: https://example.com + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:27: Found unused definition + */ - /* - this.version; - this.checkLineBreaks; - this.tagMap; - this.anchorMap; - this.tag; - this.anchor; - this.kind; - this.result;*/ +const own$1 = {}.hasOwnProperty; -} +const remarkLintNoUnusedDefinitions = lintRule( + { + origin: 'remark-lint:no-unused-definitions', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-no-unused-definitions#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + /** @type {Record} */ + const map = Object.create(null); + visit$1(tree, (node) => { + if ( + (node.type === 'definition' || node.type === 'footnoteDefinition') && + !generated(node) + ) { + map[node.identifier.toUpperCase()] = {node, used: false}; + } + }); -function generateError(state, message) { - var mark = { - name: state.filename, - buffer: state.input.slice(0, -1), // omit trailing \0 - position: state.position, - line: state.line, - column: state.position - state.lineStart - }; + visit$1(tree, (node) => { + if ( + node.type === 'imageReference' || + node.type === 'linkReference' || + node.type === 'footnoteReference' + ) { + const info = map[node.identifier.toUpperCase()]; - mark.snippet = snippet(mark); + if (!generated(node) && info) { + info.used = true; + } + } + }); - return new exception(message, mark); -} + /** @type {string} */ + let identifier; -function throwError(state, message) { - throw generateError(state, message); -} + for (identifier in map) { + if (own$1.call(map, identifier)) { + const entry = map[identifier]; -function throwWarning(state, message) { - if (state.onWarning) { - state.onWarning.call(null, generateError(state, message)); + if (!entry.used) { + file.message('Found unused definition', entry.node); + } + } + } } -} +); +var remarkLintNoUnusedDefinitions$1 = remarkLintNoUnusedDefinitions; -var directiveHandlers = { +/** + * @fileoverview + * remark preset to configure `remark-lint` with settings that prevent + * mistakes or stuff that fails across vendors. + */ - YAML: function handleYamlDirective(state, name, args) { +/** @type {Preset} */ +const remarkPresetLintRecommended = { + plugins: [ + remarkLint, + // Unix compatibility. + remarkLintFinalNewline$1, + // Rendering across vendors differs greatly if using other styles. + remarkLintListItemBulletIndent$1, + [remarkLintListItemIndent$1, 'tab-size'], + // Differs or unsupported across vendors. + remarkLintNoAutoLinkWithoutProtocol$1, + remarkLintNoBlockquoteWithoutMarker$1, + remarkLintNoLiteralUrls$1, + [remarkLintOrderedListMarkerStyle$1, '.'], + // Mistakes. + remarkLintHardBreakSpaces$1, + remarkLintNoDuplicateDefinitions$1, + remarkLintNoHeadingContentIndent$1, + remarkLintNoInlinePadding$1, + remarkLintNoShortcutReferenceImage$1, + remarkLintNoShortcutReferenceLink$1, + remarkLintNoUndefinedReferences$1, + remarkLintNoUnusedDefinitions$1 + ] +}; - var match, major, minor; +var remarkPresetLintRecommended$1 = remarkPresetLintRecommended; - if (state.version !== null) { - throwError(state, 'duplication of %YAML directive'); - } - - if (args.length !== 1) { - throwError(state, 'YAML directive accepts exactly one argument'); - } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module blockquote-indentation + * @fileoverview + * Warn when block quotes are indented too much or too little. + * + * Options: `number` or `'consistent'`, default: `'consistent'`. + * + * `'consistent'` detects the first used indentation and will warn when + * other block quotes use a different indentation. + * + * @example + * {"name": "ok.md", "setting": 4} + * + * > Hello + * + * Paragraph. + * + * > World + * @example + * {"name": "ok.md", "setting": 2} + * + * > Hello + * + * Paragraph. + * + * > World + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * > Hello + * + * Paragraph. + * + * > World + * + * Paragraph. + * + * > World + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 5:5: Remove 1 space between block quote and content + * 9:3: Add 1 space between block quote and content + */ - match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); +const remarkLintBlockquoteIndentation = lintRule( + { + origin: 'remark-lint:blockquote-indentation', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-blockquote-indentation#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + visit$1(tree, 'blockquote', (node) => { + if (generated(node) || node.children.length === 0) { + return + } - if (match === null) { - throwError(state, 'ill-formed argument of the YAML directive'); - } + if (option === 'consistent') { + option = check$1(node); + } else { + const diff = option - check$1(node); - major = parseInt(match[1], 10); - minor = parseInt(match[2], 10); + if (diff !== 0) { + const abs = Math.abs(diff); - if (major !== 1) { - throwError(state, 'unacceptable YAML version of the document'); - } + file.message( + (diff > 0 ? 'Add' : 'Remove') + + ' ' + + abs + + ' ' + + plural('space', abs) + + ' between block quote and content', + pointStart(node.children[0]) + ); + } + } + }); + } +); - state.version = args[0]; - state.checkLineBreaks = (minor < 2); +var remarkLintBlockquoteIndentation$1 = remarkLintBlockquoteIndentation; - if (minor !== 1 && minor !== 2) { - throwWarning(state, 'unsupported YAML version of the document'); - } - }, +/** + * @param {Blockquote} node + * @returns {number} + */ +function check$1(node) { + return pointStart(node.children[0]).column - pointStart(node).column +} - TAG: function handleTagDirective(state, name, args) { +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module checkbox-character-style + * @fileoverview + * Warn when list item checkboxes violate a given style. + * + * Options: `Object` or `'consistent'`, default: `'consistent'`. + * + * `'consistent'` detects the first used checked and unchecked checkbox + * styles and warns when subsequent checkboxes use different styles. + * + * Styles can also be passed in like so: + * + * ```js + * {checked: 'x', unchecked: ' '} + * ``` + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats checked checkboxes using `x` (lowercase X) and unchecked checkboxes + * as `·` (a single space). + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md", "setting": {"checked": "x"}, "gfm": true} + * + * - [x] List item + * - [x] List item + * + * @example + * {"name": "ok.md", "setting": {"checked": "X"}, "gfm": true} + * + * - [X] List item + * - [X] List item + * + * @example + * {"name": "ok.md", "setting": {"unchecked": " "}, "gfm": true} + * + * - [ ] List item + * - [ ] List item + * - [ ]·· + * - [ ] + * + * @example + * {"name": "ok.md", "setting": {"unchecked": "\t"}, "gfm": true} + * + * - [»] List item + * - [»] List item + * + * @example + * {"name": "not-ok.md", "label": "input", "gfm": true} + * + * - [x] List item + * - [X] List item + * - [ ] List item + * - [»] List item + * + * @example + * {"name": "not-ok.md", "label": "output", "gfm": true} + * + * 2:5: Checked checkboxes should use `x` as a marker + * 4:5: Unchecked checkboxes should use ` ` as a marker + * + * @example + * {"setting": {"unchecked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true} + * + * 1:1: Incorrect unchecked checkbox marker `💩`: use either `'\t'`, or `' '` + * + * @example + * {"setting": {"checked": "💩"}, "name": "not-ok.md", "label": "output", "positionless": true, "gfm": true} + * + * 1:1: Incorrect checked checkbox marker `💩`: use either `'x'`, or `'X'` + */ - var handle, prefix; +const remarkLintCheckboxCharacterStyle = lintRule( + { + origin: 'remark-lint:checkbox-character-style', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-checkbox-character-style#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); + /** @type {'x'|'X'|'consistent'} */ + let checked = 'consistent'; + /** @type {' '|'\x09'|'consistent'} */ + let unchecked = 'consistent'; - if (args.length !== 2) { - throwError(state, 'TAG directive accepts exactly two arguments'); + if (typeof option === 'object') { + checked = option.checked || 'consistent'; + unchecked = option.unchecked || 'consistent'; } - handle = args[0]; - prefix = args[1]; - - if (!PATTERN_TAG_HANDLE.test(handle)) { - throwError(state, 'ill-formed tag handle (first argument) of the TAG directive'); + if (unchecked !== 'consistent' && unchecked !== ' ' && unchecked !== '\t') { + file.fail( + 'Incorrect unchecked checkbox marker `' + + unchecked + + "`: use either `'\\t'`, or `' '`" + ); } - if (_hasOwnProperty$1.call(state.tagMap, handle)) { - throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle'); + if (checked !== 'consistent' && checked !== 'x' && checked !== 'X') { + file.fail( + 'Incorrect checked checkbox marker `' + + checked + + "`: use either `'x'`, or `'X'`" + ); } - if (!PATTERN_TAG_URI.test(prefix)) { - throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive'); - } + visit$1(tree, 'listItem', (node) => { + const head = node.children[0]; + const point = pointStart(head); - try { - prefix = decodeURIComponent(prefix); - } catch (err) { - throwError(state, 'tag prefix is malformed: ' + prefix); - } + // Exit early for items without checkbox. + // A list item cannot be checked and empty, according to GFM. + if ( + typeof node.checked !== 'boolean' || + !head || + typeof point.offset !== 'number' + ) { + return + } - state.tagMap[handle] = prefix; - } -}; + // Move back to before `] `. + point.offset -= 2; + point.column -= 2; + // Assume we start with a checkbox, because well, `checked` is set. + const match = /\[([\t Xx])]/.exec( + value.slice(point.offset - 2, point.offset + 1) + ); -function captureSegment(state, start, end, checkJson) { - var _position, _length, _character, _result; + // Failsafe to make sure we don‘t crash if there actually isn’t a checkbox. + /* c8 ignore next */ + if (!match) return - if (start < end) { - _result = state.input.slice(start, end); + const style = node.checked ? checked : unchecked; - if (checkJson) { - for (_position = 0, _length = _result.length; _position < _length; _position += 1) { - _character = _result.charCodeAt(_position); - if (!(_character === 0x09 || - (0x20 <= _character && _character <= 0x10FFFF))) { - throwError(state, 'expected valid JSON character'); + if (style === 'consistent') { + if (node.checked) { + // @ts-expect-error: valid marker. + checked = match[1]; + } else { + // @ts-expect-error: valid marker. + unchecked = match[1]; } + } else if (match[1] !== style) { + file.message( + (node.checked ? 'Checked' : 'Unchecked') + + ' checkboxes should use `' + + style + + '` as a marker', + point + ); } - } else if (PATTERN_NON_PRINTABLE.test(_result)) { - throwError(state, 'the stream contains non-printable characters'); - } - - state.result += _result; + }); } -} +); -function mergeMappings(state, destination, source, overridableKeys) { - var sourceKeys, key, index, quantity; +var remarkLintCheckboxCharacterStyle$1 = remarkLintCheckboxCharacterStyle; - if (!common.isObject(source)) { - throwError(state, 'cannot merge mappings; the provided source object is unacceptable'); - } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module checkbox-content-indent + * @fileoverview + * Warn when list item checkboxes are followed by too much whitespace. + * + * @example + * {"name": "ok.md", "gfm": true} + * + * - [ ] List item + * + [x] List Item + * * [X] List item + * - [ ] List item + * + * @example + * {"name": "not-ok.md", "label": "input", "gfm": true} + * + * - [ ] List item + * + [x] List item + * * [X] List item + * - [ ] List item + * + * @example + * {"name": "not-ok.md", "label": "output", "gfm": true} + * + * 2:7-2:8: Checkboxes should be followed by a single character + * 3:7-3:9: Checkboxes should be followed by a single character + * 4:7-4:10: Checkboxes should be followed by a single character + */ - sourceKeys = Object.keys(source); +const remarkLintCheckboxContentIndent = lintRule( + { + origin: 'remark-lint:checkbox-content-indent', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-checkbox-content-indent#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + const value = String(file); + const loc = location(file); - for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) { - key = sourceKeys[index]; + visit$1(tree, 'listItem', (node) => { + const head = node.children[0]; + const point = pointStart(head); - if (!_hasOwnProperty$1.call(destination, key)) { - destination[key] = source[key]; - overridableKeys[key] = true; - } - } -} + // Exit early for items without checkbox. + // A list item cannot be checked and empty, according to GFM. + if ( + typeof node.checked !== 'boolean' || + !head || + typeof point.offset !== 'number' + ) { + return + } -function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, - startLine, startLineStart, startPos) { + // Assume we start with a checkbox, because well, `checked` is set. + const match = /\[([\t xX])]/.exec( + value.slice(point.offset - 4, point.offset + 1) + ); - var index, quantity; + // Failsafe to make sure we don‘t crash if there actually isn’t a checkbox. + /* c8 ignore next */ + if (!match) return - // The output is a plain object here, so keys can only be strings. - // We need to convert keyNode to a string, but doing so can hang the process - // (deeply nested arrays that explode exponentially using aliases). - if (Array.isArray(keyNode)) { - keyNode = Array.prototype.slice.call(keyNode); + // Move past checkbox. + const initial = point.offset; + let final = initial; - for (index = 0, quantity = keyNode.length; index < quantity; index += 1) { - if (Array.isArray(keyNode[index])) { - throwError(state, 'nested arrays are not supported inside keys'); - } + while (/[\t ]/.test(value.charAt(final))) final++; - if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') { - keyNode[index] = '[object Object]'; + if (final - initial > 0) { + file.message('Checkboxes should be followed by a single character', { + start: loc.toPoint(initial), + end: loc.toPoint(final) + }); } - } + }); } +); - // Avoid code execution in load() via toString property - // (still use its own toString for arrays, timestamps, - // and whatever user schema extensions happen to have @@toStringTag) - if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') { - keyNode = '[object Object]'; - } +var remarkLintCheckboxContentIndent$1 = remarkLintCheckboxContentIndent; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module code-block-style + * @fileoverview + * Warn when code blocks do not adhere to a given style. + * + * Options: `'consistent'`, `'fenced'`, or `'indented'`, default: `'consistent'`. + * + * `'consistent'` detects the first used code block style and warns when + * subsequent code blocks uses different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats code blocks using a fence if they have a language flag and + * indentation if not. + * Pass + * [`fences: true`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsfences) + * to always use fences for code blocks. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"setting": "indented", "name": "ok.md"} + * + * alpha() + * + * Paragraph. + * + * bravo() + * + * @example + * {"setting": "indented", "name": "not-ok.md", "label": "input"} + * + * ``` + * alpha() + * ``` + * + * Paragraph. + * + * ``` + * bravo() + * ``` + * + * @example + * {"setting": "indented", "name": "not-ok.md", "label": "output"} + * + * 1:1-3:4: Code blocks should be indented + * 7:1-9:4: Code blocks should be indented + * + * @example + * {"setting": "fenced", "name": "ok.md"} + * + * ``` + * alpha() + * ``` + * + * Paragraph. + * + * ``` + * bravo() + * ``` + * + * @example + * {"setting": "fenced", "name": "not-ok-fenced.md", "label": "input"} + * + * alpha() + * + * Paragraph. + * + * bravo() + * + * @example + * {"setting": "fenced", "name": "not-ok-fenced.md", "label": "output"} + * + * 1:1-1:12: Code blocks should be fenced + * 5:1-5:12: Code blocks should be fenced + * + * @example + * {"name": "not-ok-consistent.md", "label": "input"} + * + * alpha() + * + * Paragraph. + * + * ``` + * bravo() + * ``` + * + * @example + * {"name": "not-ok-consistent.md", "label": "output"} + * + * 5:1-7:4: Code blocks should be indented + * + * @example + * {"setting": "💩", "name": "not-ok-incorrect.md", "label": "output", "positionless": true} + * + * 1:1: Incorrect code block style `💩`: use either `'consistent'`, `'fenced'`, or `'indented'` + */ - keyNode = String(keyNode); +const remarkLintCodeBlockStyle = lintRule( + { + origin: 'remark-lint:code-block-style', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-code-block-style#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); - if (_result === null) { - _result = {}; - } - - if (keyTag === 'tag:yaml.org,2002:merge') { - if (Array.isArray(valueNode)) { - for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { - mergeMappings(state, _result, valueNode[index], overridableKeys); - } - } else { - mergeMappings(state, _result, valueNode, overridableKeys); - } - } else { - if (!state.json && - !_hasOwnProperty$1.call(overridableKeys, keyNode) && - _hasOwnProperty$1.call(_result, keyNode)) { - state.line = startLine || state.line; - state.lineStart = startLineStart || state.lineStart; - state.position = startPos || state.position; - throwError(state, 'duplicated mapping key'); - } - - // used for this specific key only because Object.defineProperty is slow - if (keyNode === '__proto__') { - Object.defineProperty(_result, keyNode, { - configurable: true, - enumerable: true, - writable: true, - value: valueNode - }); - } else { - _result[keyNode] = valueNode; + if ( + option !== 'consistent' && + option !== 'fenced' && + option !== 'indented' + ) { + file.fail( + 'Incorrect code block style `' + + option + + "`: use either `'consistent'`, `'fenced'`, or `'indented'`" + ); } - delete overridableKeys[keyNode]; - } - return _result; -} + visit$1(tree, 'code', (node) => { + if (generated(node)) { + return + } -function readLineBreak(state) { - var ch; + const initial = pointStart(node).offset; + const final = pointEnd(node).offset; - ch = state.input.charCodeAt(state.position); + const current = + node.lang || /^\s*([~`])\1{2,}/.test(value.slice(initial, final)) + ? 'fenced' + : 'indented'; - if (ch === 0x0A/* LF */) { - state.position++; - } else if (ch === 0x0D/* CR */) { - state.position++; - if (state.input.charCodeAt(state.position) === 0x0A/* LF */) { - state.position++; - } - } else { - throwError(state, 'a line break is expected'); + if (option === 'consistent') { + option = current; + } else if (option !== current) { + file.message('Code blocks should be ' + option, node); + } + }); } +); - state.line += 1; - state.lineStart = state.position; - state.firstTabInLine = -1; -} +var remarkLintCodeBlockStyle$1 = remarkLintCodeBlockStyle; -function skipSeparationSpace(state, allowComments, checkIndent) { - var lineBreaks = 0, - ch = state.input.charCodeAt(state.position); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module definition-spacing + * @fileoverview + * Warn when consecutive whitespace is used in a definition. + * + * @example + * {"name": "ok.md"} + * + * [example domain]: http://example.com "Example Domain" + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * [example····domain]: http://example.com "Example Domain" + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-1:57: Do not use consecutive whitespace in definition labels + */ - while (ch !== 0) { - while (is_WHITE_SPACE(ch)) { - if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) { - state.firstTabInLine = state.position; - } - ch = state.input.charCodeAt(++state.position); - } +const label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/; - if (allowComments && ch === 0x23/* # */) { - do { - ch = state.input.charCodeAt(++state.position); - } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0); - } +const remarkLintDefinitionSpacing = lintRule( + { + origin: 'remark-lint:definition-spacing', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-definition-spacing#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + const value = String(file); - if (is_EOL(ch)) { - readLineBreak(state); + visit$1(tree, (node) => { + if (node.type === 'definition' || node.type === 'footnoteDefinition') { + const start = pointStart(node).offset; + const end = pointEnd(node).offset; - ch = state.input.charCodeAt(state.position); - lineBreaks++; - state.lineIndent = 0; + if (typeof start === 'number' && typeof end === 'number') { + const match = value.slice(start, end).match(label); - while (ch === 0x20/* Space */) { - state.lineIndent++; - ch = state.input.charCodeAt(++state.position); + if (match && /[ \t\n]{2,}/.test(match[1])) { + file.message( + 'Do not use consecutive whitespace in definition labels', + node + ); + } + } } - } else { - break; - } + }); } +); - if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { - throwWarning(state, 'deficient indentation'); - } +var remarkLintDefinitionSpacing$1 = remarkLintDefinitionSpacing; - return lineBreaks; -} +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module fenced-code-flag + * @fileoverview + * Check fenced code block flags. + * + * Options: `Array.` or `Object`, optional. + * + * Providing an array is as passing `{flags: Array}`. + * + * The object can have an array of `'flags'` which are allowed: other flags + * will not be allowed. + * An `allowEmpty` field (`boolean`, default: `false`) can be set to allow + * code blocks without language flags. + * + * @example + * {"name": "ok.md"} + * + * ```alpha + * bravo() + * ``` + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * ``` + * alpha() + * ``` + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:1-3:4: Missing code language flag + * + * @example + * {"name": "ok.md", "setting": {"allowEmpty": true}} + * + * ``` + * alpha() + * ``` + * + * @example + * {"name": "not-ok.md", "setting": {"allowEmpty": false}, "label": "input"} + * + * ``` + * alpha() + * ``` + * + * @example + * {"name": "not-ok.md", "setting": {"allowEmpty": false}, "label": "output"} + * + * 1:1-3:4: Missing code language flag + * + * @example + * {"name": "ok.md", "setting": ["alpha"]} + * + * ```alpha + * bravo() + * ``` + * + * @example + * {"name": "ok.md", "setting": {"flags":["alpha"]}} + * + * ```alpha + * bravo() + * ``` + * + * @example + * {"name": "not-ok.md", "setting": ["charlie"], "label": "input"} + * + * ```alpha + * bravo() + * ``` + * + * @example + * {"name": "not-ok.md", "setting": ["charlie"], "label": "output"} + * + * 1:1-3:4: Incorrect code language flag + */ -function testDocumentSeparator(state) { - var _position = state.position, - ch; +const fence = /^ {0,3}([~`])\1{2,}/; - ch = state.input.charCodeAt(_position); +const remarkLintFencedCodeFlag = lintRule( + { + origin: 'remark-lint:fenced-code-flag', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-fenced-code-flag#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option) => { + const value = String(file); + let allowEmpty = false; + /** @type {string[]} */ + let allowed = []; - // Condition state.position === state.lineStart is tested - // in parent on each call, for efficiency. No needs to test here again. - if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) && - ch === state.input.charCodeAt(_position + 1) && - ch === state.input.charCodeAt(_position + 2)) { - - _position += 3; - - ch = state.input.charCodeAt(_position); + if (typeof option === 'object') { + if (Array.isArray(option)) { + allowed = option; + } else { + allowEmpty = Boolean(option.allowEmpty); - if (ch === 0 || is_WS_OR_EOL(ch)) { - return true; + if (option.flags) { + allowed = option.flags; + } + } } - } - return false; -} + visit$1(tree, 'code', (node) => { + if (!generated(node)) { + if (node.lang) { + if (allowed.length > 0 && !allowed.includes(node.lang)) { + file.message('Incorrect code language flag', node); + } + } else { + const slice = value.slice( + pointStart(node).offset, + pointEnd(node).offset + ); -function writeFoldedLines(state, count) { - if (count === 1) { - state.result += ' '; - } else if (count > 1) { - state.result += common.repeat('\n', count - 1); + if (!allowEmpty && fence.test(slice)) { + file.message('Missing code language flag', node); + } + } + } + }); } -} - - -function readPlainScalar(state, nodeIndent, withinFlowCollection) { - var preceding, - following, - captureStart, - captureEnd, - hasPendingContent, - _line, - _lineStart, - _lineIndent, - _kind = state.kind, - _result = state.result, - ch; +); - ch = state.input.charCodeAt(state.position); +var remarkLintFencedCodeFlag$1 = remarkLintFencedCodeFlag; - if (is_WS_OR_EOL(ch) || - is_FLOW_INDICATOR(ch) || - ch === 0x23/* # */ || - ch === 0x26/* & */ || - ch === 0x2A/* * */ || - ch === 0x21/* ! */ || - ch === 0x7C/* | */ || - ch === 0x3E/* > */ || - ch === 0x27/* ' */ || - ch === 0x22/* " */ || - ch === 0x25/* % */ || - ch === 0x40/* @ */ || - ch === 0x60/* ` */) { - return false; - } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module fenced-code-marker + * @fileoverview + * Warn for violating fenced code markers. + * + * Options: `` '`' ``, `'~'`, or `'consistent'`, default: `'consistent'`. + * + * `'consistent'` detects the first used fenced code marker style and warns + * when subsequent fenced code blocks use different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats fences using ``'`'`` (grave accent) by default. + * Pass + * [`fence: '~'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsfence) + * to use `~` (tilde) instead. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * Indented code blocks are not affected by this rule: + * + * bravo() + * + * @example + * {"name": "ok.md", "setting": "`"} + * + * ```alpha + * bravo() + * ``` + * + * ``` + * charlie() + * ``` + * + * @example + * {"name": "ok.md", "setting": "~"} + * + * ~~~alpha + * bravo() + * ~~~ + * + * ~~~ + * charlie() + * ~~~ + * + * @example + * {"name": "not-ok-consistent-tick.md", "label": "input"} + * + * ```alpha + * bravo() + * ``` + * + * ~~~ + * charlie() + * ~~~ + * + * @example + * {"name": "not-ok-consistent-tick.md", "label": "output"} + * + * 5:1-7:4: Fenced code should use `` ` `` as a marker + * + * @example + * {"name": "not-ok-consistent-tilde.md", "label": "input"} + * + * ~~~alpha + * bravo() + * ~~~ + * + * ``` + * charlie() + * ``` + * + * @example + * {"name": "not-ok-consistent-tilde.md", "label": "output"} + * + * 5:1-7:4: Fenced code should use `~` as a marker + * + * @example + * {"name": "not-ok-incorrect.md", "setting": "💩", "label": "output", "positionless": true} + * + * 1:1: Incorrect fenced code marker `💩`: use either `'consistent'`, `` '`' ``, or `'~'` + */ - if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { - following = state.input.charCodeAt(state.position + 1); +const remarkLintFencedCodeMarker = lintRule( + { + origin: 'remark-lint:fenced-code-marker', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-fenced-code-marker#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const contents = String(file); - if (is_WS_OR_EOL(following) || - withinFlowCollection && is_FLOW_INDICATOR(following)) { - return false; + if (option !== 'consistent' && option !== '~' && option !== '`') { + file.fail( + 'Incorrect fenced code marker `' + + option + + "`: use either `'consistent'`, `` '`' ``, or `'~'`" + ); } - } - state.kind = 'scalar'; - state.result = ''; - captureStart = captureEnd = state.position; - hasPendingContent = false; + visit$1(tree, 'code', (node) => { + const start = pointStart(node).offset; - while (ch !== 0) { - if (ch === 0x3A/* : */) { - following = state.input.charCodeAt(state.position + 1); + if (typeof start === 'number') { + const marker = contents + .slice(start, start + 4) + .replace(/^\s+/, '') + .charAt(0); - if (is_WS_OR_EOL(following) || - withinFlowCollection && is_FLOW_INDICATOR(following)) { - break; + // Ignore unfenced code blocks. + if (marker === '~' || marker === '`') { + if (option === 'consistent') { + option = marker; + } else if (marker !== option) { + file.message( + 'Fenced code should use `' + + (option === '~' ? option : '` ` `') + + '` as a marker', + node + ); + } + } } + }); + } +); - } else if (ch === 0x23/* # */) { - preceding = state.input.charCodeAt(state.position - 1); - - if (is_WS_OR_EOL(preceding)) { - break; - } +var remarkLintFencedCodeMarker$1 = remarkLintFencedCodeMarker; - } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || - withinFlowCollection && is_FLOW_INDICATOR(ch)) { - break; +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module file-extension + * @fileoverview + * Warn when the file extension differ from the preferred extension. + * + * Does not warn when given documents have no file extensions (such as + * `AUTHORS` or `LICENSE`). + * + * Options: `string`, default: `'md'` — Expected file extension. + * + * @example + * {"name": "readme.md"} + * + * @example + * {"name": "readme"} + * + * @example + * {"name": "readme.mkd", "label": "output", "positionless": true} + * + * 1:1: Incorrect extension: use `md` + * + * @example + * {"name": "readme.mkd", "setting": "mkd"} + */ - } else if (is_EOL(ch)) { - _line = state.line; - _lineStart = state.lineStart; - _lineIndent = state.lineIndent; - skipSeparationSpace(state, false, -1); +const remarkLintFileExtension = lintRule( + { + origin: 'remark-lint:file-extension', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-file-extension#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (_, file, option = 'md') => { + const ext = file.extname; - if (state.lineIndent >= nodeIndent) { - hasPendingContent = true; - ch = state.input.charCodeAt(state.position); - continue; - } else { - state.position = captureEnd; - state.line = _line; - state.lineStart = _lineStart; - state.lineIndent = _lineIndent; - break; - } + if (ext && ext.slice(1) !== option) { + file.message('Incorrect extension: use `' + option + '`'); } + } +); - if (hasPendingContent) { - captureSegment(state, captureStart, captureEnd, false); - writeFoldedLines(state, state.line - _line); - captureStart = captureEnd = state.position; - hasPendingContent = false; +var remarkLintFileExtension$1 = remarkLintFileExtension; + +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module final-definition + * @fileoverview + * Warn when definitions are placed somewhere other than at the end of + * the file. + * + * @example + * {"name": "ok.md"} + * + * Paragraph. + * + * [example]: http://example.com "Example Domain" + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * Paragraph. + * + * [example]: http://example.com "Example Domain" + * + * Another paragraph. + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 3:1-3:47: Move definitions to the end of the file (after the node at line `5`) + * + * @example + * {"name": "ok-comments.md"} + * + * Paragraph. + * + * [example-1]: http://example.com/one/ + * + * + * + * [example-2]: http://example.com/two/ + */ + +const remarkLintFinalDefinition = lintRule( + { + origin: 'remark-lint:final-definition', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-final-definition#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + let last = 0; + + visit$1( + tree, + (node) => { + // Ignore generated and HTML comment nodes. + if ( + node.type === 'root' || + generated(node) || + (node.type === 'html' && /^\s*".length)); + if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { + following = state.input.charCodeAt(state.position + 1); - validateMeta(node, file, meta); - } catch (e) { - file.message(e, node); + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + return false; } - }); -} + } -const remarkLintNodejsYamlComments = lintRule( - "remark-lint:nodejs-yaml-comments", - validateYAMLComments -); + state.kind = 'scalar'; + state.result = ''; + captureStart = captureEnd = state.position; + hasPendingContent = false; -function escapeStringRegexp(string) { - if (typeof string !== 'string') { - throw new TypeError('Expected a string'); - } + while (ch !== 0) { + if (ch === 0x3A/* : */) { + following = state.input.charCodeAt(state.position + 1); - // Escape characters with special meaning either inside or outside character sets. - // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. - return string - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d'); -} + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + break; + } -const remarkLintProhibitedStrings = lintRule('remark-lint:prohibited-strings', prohibitedStrings); + } else if (ch === 0x23/* # */) { + preceding = state.input.charCodeAt(state.position - 1); -function testProhibited (val, content) { - let regexpFlags = 'g'; - let no = val.no; + if (is_WS_OR_EOL(preceding)) { + break; + } - if (!no) { - no = escapeStringRegexp(val.yes); - regexpFlags += 'i'; - } + } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || + withinFlowCollection && is_FLOW_INDICATOR(ch)) { + break; - let regexpString = '(?= nodeIndent) { + hasPendingContent = true; + ch = state.input.charCodeAt(state.position); + continue; + } else { + state.position = captureEnd; + state.line = _line; + state.lineStart = _lineStart; + state.lineIndent = _lineIndent; + break; + } + } - // If it starts with a letter, make sure it is a word break. - if (/^\b/.test(no)) { - regexpString += '\\b'; - } - if (ignoreNextTo) { - regexpString += `(? { - const results = testProhibited(val, content); - if (results.length) { - results.forEach(({ result, index, yes }) => { - const message = val.yes ? `Use "${yes}" instead of "${result}"` : `Do not use "${result}"`; - file.message(message, { - start: myLocation.toPoint(initial + index), - end: myLocation.toPoint(initial + index + [...result].length) - }); - }); + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x27/* ' */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x27/* ' */) { + captureStart = state.position; + state.position++; + captureEnd = state.position; + } else { + return true; } - }); - } -} -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module rule-style - * @fileoverview - * Warn when the thematic breaks (horizontal rules) violate a given or - * detected style. - * - * Options: `string`, either a corect thematic breaks such as `***`, or - * `'consistent'`, default: `'consistent'`. - * - * `'consistent'` detects the first used thematic break style and warns when - * subsequent rules use different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * has three settings that define how rules are created: - * - * * [`rule`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrule) - * (default: `*`) — Marker to use - * * [`ruleRepetition`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrulerepetition) - * (default: `3`) — Number of markers to use - * * [`ruleSpaces`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrulespaces) - * (default: `true`) — Whether to pad markers with spaces - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md", "setting": "* * *"} - * - * * * * - * - * * * * - * - * @example - * {"name": "ok.md", "setting": "_______"} - * - * _______ - * - * _______ - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * *** - * - * * * * - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 3:1-3:6: Rules should use `***` - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} - * - * 1:1: Incorrect preferred rule style: provide a correct markdown rule or `'consistent'` - */ + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; -const remarkLintRuleStyle = lintRule( - { - origin: 'remark-lint:rule-style', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-rule-style#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a single quoted scalar'); - if (option !== 'consistent' && /[^-_* ]/.test(option)) { - file.fail( - "Incorrect preferred rule style: provide a correct markdown rule or `'consistent'`" - ); + } else { + state.position++; + captureEnd = state.position; } + } - visit$1(tree, 'thematicBreak', (node) => { - const initial = pointStart(node).offset; - const final = pointEnd(node).offset; + throwError(state, 'unexpected end of the stream within a single quoted scalar'); +} - if (typeof initial === 'number' && typeof final === 'number') { - const rule = value.slice(initial, final); +function readDoubleQuotedScalar(state, nodeIndent) { + var captureStart, + captureEnd, + hexLength, + hexResult, + tmp, + ch; - if (option === 'consistent') { - option = rule; - } else if (rule !== option) { - file.message('Rules should use `' + option + '`', node); - } - } - }); + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x22/* " */) { + return false; } -); -var remarkLintRuleStyle$1 = remarkLintRuleStyle; + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module strong-marker - * @fileoverview - * Warn for violating importance (strong) markers. - * - * Options: `'consistent'`, `'*'`, or `'_'`, default: `'consistent'`. - * - * `'consistent'` detects the first used importance style and warns when - * subsequent importance sequences use different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats importance using an `*` (asterisk) by default. - * Pass - * [`strong: '_'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsstrong) - * to use `_` (underscore) instead. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * **foo** and **bar**. - * - * @example - * {"name": "also-ok.md"} - * - * __foo__ and __bar__. - * - * @example - * {"name": "ok.md", "setting": "*"} - * - * **foo**. - * - * @example - * {"name": "ok.md", "setting": "_"} - * - * __foo__. - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * **foo** and __bar__. - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 1:13-1:20: Strong should use `*` as a marker - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} - * - * 1:1: Incorrect strong marker `💩`: use either `'consistent'`, `'*'`, or `'_'` - */ + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x22/* " */) { + captureSegment(state, captureStart, state.position, true); + state.position++; + return true; + + } else if (ch === 0x5C/* \ */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); -const remarkLintStrongMarker = lintRule( - { - origin: 'remark-lint:strong-marker', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-strong-marker#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); + if (is_EOL(ch)) { + skipSeparationSpace(state, false, nodeIndent); - if (option !== '*' && option !== '_' && option !== 'consistent') { - file.fail( - 'Incorrect strong marker `' + - option + - "`: use either `'consistent'`, `'*'`, or `'_'`" - ); - } + // TODO: rework to inline fn with no type cast? + } else if (ch < 256 && simpleEscapeCheck[ch]) { + state.result += simpleEscapeMap[ch]; + state.position++; - visit$1(tree, 'strong', (node) => { - const start = pointStart(node).offset; + } else if ((tmp = escapedHexLen(ch)) > 0) { + hexLength = tmp; + hexResult = 0; - if (typeof start === 'number') { - const marker = /** @type {Marker} */ (value.charAt(start)); + for (; hexLength > 0; hexLength--) { + ch = state.input.charCodeAt(++state.position); - if (option === 'consistent') { - option = marker; - } else if (marker !== option) { - file.message('Strong should use `' + option + '` as a marker', node); + if ((tmp = fromHexCode(ch)) >= 0) { + hexResult = (hexResult << 4) + tmp; + + } else { + throwError(state, 'expected hexadecimal character'); + } } + + state.result += charFromCodepoint(hexResult); + + state.position++; + + } else { + throwError(state, 'unknown escape sequence'); } - }); + + captureStart = captureEnd = state.position; + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a double quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } } -); -var remarkLintStrongMarker$1 = remarkLintStrongMarker; + throwError(state, 'unexpected end of the stream within a double quoted scalar'); +} -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module table-cell-padding - * @fileoverview - * Warn when table cells are incorrectly padded. - * - * Options: `'consistent'`, `'padded'`, or `'compact'`, default: `'consistent'`. - * - * `'consistent'` detects the first used cell padding style and warns when - * subsequent cells use different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats tables with padding by default. - * Pass - * [`spacedTable: false`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsspacedtable) - * to not use padding. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md", "setting": "padded", "gfm": true} - * - * | A | B | - * | ----- | ----- | - * | Alpha | Bravo | - * - * @example - * {"name": "not-ok.md", "label": "input", "setting": "padded", "gfm": true} - * - * | A | B | - * | :----|----: | - * | Alpha|Bravo | - * - * | C | D | - * | :----- | ---: | - * |Charlie | Delta| - * - * Too much padding isn’t good either: - * - * | E | F | G | H | - * | :---- | -------- | :----: | -----: | - * | Echo | Foxtrot | Golf | Hotel | - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "padded", "gfm": true} - * - * 3:8: Cell should be padded - * 3:9: Cell should be padded - * 7:2: Cell should be padded - * 7:17: Cell should be padded - * 13:9: Cell should be padded with 1 space, not 2 - * 13:20: Cell should be padded with 1 space, not 2 - * 13:21: Cell should be padded with 1 space, not 2 - * 13:29: Cell should be padded with 1 space, not 2 - * 13:30: Cell should be padded with 1 space, not 2 - * - * @example - * {"name": "ok.md", "setting": "compact", "gfm": true} - * - * |A |B | - * |-----|-----| - * |Alpha|Bravo| - * - * @example - * {"name": "not-ok.md", "label": "input", "setting": "compact", "gfm": true} - * - * | A | B | - * | -----| -----| - * | Alpha| Bravo| - * - * |C | D| - * |:------|-----:| - * |Charlie|Delta | - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "compact", "gfm": true} - * - * 3:2: Cell should be compact - * 3:11: Cell should be compact - * 7:16: Cell should be compact - * - * @example - * {"name": "ok-padded.md", "setting": "consistent", "gfm": true} - * - * | A | B | - * | ----- | ----- | - * | Alpha | Bravo | - * - * | C | D | - * | ------- | ----- | - * | Charlie | Delta | - * - * @example - * {"name": "not-ok-padded.md", "label": "input", "setting": "consistent", "gfm": true} - * - * | A | B | - * | ----- | ----- | - * | Alpha | Bravo | - * - * | C | D | - * | :----- | ----: | - * |Charlie | Delta | - * - * @example - * {"name": "not-ok-padded.md", "label": "output", "setting": "consistent", "gfm": true} - * - * 7:2: Cell should be padded - * - * @example - * {"name": "ok-compact.md", "setting": "consistent", "gfm": true} - * - * |A |B | - * |-----|-----| - * |Alpha|Bravo| - * - * |C |D | - * |-------|-----| - * |Charlie|Delta| - * - * @example - * {"name": "not-ok-compact.md", "label": "input", "setting": "consistent", "gfm": true} - * - * |A |B | - * |-----|-----| - * |Alpha|Bravo| - * - * |C | D| - * |:------|-----:| - * |Charlie|Delta | - * - * @example - * {"name": "not-ok-compact.md", "label": "output", "setting": "consistent", "gfm": true} - * - * 7:16: Cell should be compact - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true, "gfm": true} - * - * 1:1: Incorrect table cell padding style `💩`, expected `'padded'`, `'compact'`, or `'consistent'` - * - * @example - * {"name": "empty.md", "label": "input", "setting": "padded", "gfm": true} - * - * - * - * | | Alpha | Bravo| - * | ------ | ----- | ---: | - * | Charlie| | Echo| - * - * @example - * {"name": "empty.md", "label": "output", "setting": "padded", "gfm": true} - * - * 3:25: Cell should be padded - * 5:10: Cell should be padded - * 5:25: Cell should be padded - * - * @example - * {"name": "missing-body.md", "setting": "padded", "gfm": true} - * - * - * - * | Alpha | Bravo | Charlie | - * | ----- | ------- | ------- | - * | Delta | - * | Echo | Foxtrot | - */ +function readFlowCollection(state, nodeIndent) { + var readNext = true, + _line, + _lineStart, + _pos, + _tag = state.tag, + _result, + _anchor = state.anchor, + following, + terminator, + isPair, + isExplicitPair, + isMapping, + overridableKeys = Object.create(null), + keyNode, + keyTag, + valueNode, + ch; -const remarkLintTableCellPadding = lintRule( - { - origin: 'remark-lint:table-cell-padding', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-table-cell-padding#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - if ( - option !== 'padded' && - option !== 'compact' && - option !== 'consistent' - ) { - file.fail( - 'Incorrect table cell padding style `' + - option + - "`, expected `'padded'`, `'compact'`, or `'consistent'`" - ); - } + ch = state.input.charCodeAt(state.position); - visit$1(tree, 'table', (node) => { - const rows = node.children; - // To do: fix types to always have `align` defined. - /* c8 ignore next */ - const align = node.align || []; - /** @type {number[]} */ - const sizes = Array.from({length: align.length}); - /** @type {Entry[]} */ - const entries = []; - let index = -1; + if (ch === 0x5B/* [ */) { + terminator = 0x5D;/* ] */ + isMapping = false; + _result = []; + } else if (ch === 0x7B/* { */) { + terminator = 0x7D;/* } */ + isMapping = true; + _result = {}; + } else { + return false; + } - // Check rows. - while (++index < rows.length) { - const row = rows[index]; - let column = -1; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } - // Check fences (before, between, and after cells). - while (++column < row.children.length) { - const cell = row.children[column]; + ch = state.input.charCodeAt(++state.position); - if (cell.children.length > 0) { - const cellStart = pointStart(cell).offset; - const cellEnd = pointEnd(cell).offset; - const contentStart = pointStart(cell.children[0]).offset; - const contentEnd = pointEnd( - cell.children[cell.children.length - 1] - ).offset; + while (ch !== 0) { + skipSeparationSpace(state, true, nodeIndent); - if ( - typeof cellStart !== 'number' || - typeof cellEnd !== 'number' || - typeof contentStart !== 'number' || - typeof contentEnd !== 'number' - ) { - continue - } + ch = state.input.charCodeAt(state.position); - entries.push({ - node: cell, - start: contentStart - cellStart - (column ? 0 : 1), - end: cellEnd - contentEnd - 1, - column - }); + if (ch === terminator) { + state.position++; + state.tag = _tag; + state.anchor = _anchor; + state.kind = isMapping ? 'mapping' : 'sequence'; + state.result = _result; + return true; + } else if (!readNext) { + throwError(state, 'missed comma between flow collection entries'); + } else if (ch === 0x2C/* , */) { + // "flow collection entries can never be completely empty", as per YAML 1.2, section 7.4 + throwError(state, "expected the node content, but found ','"); + } - // Detect max space per column. - sizes[column] = Math.max( - sizes[column] || 0, - contentEnd - contentStart - ); - } - } + keyTag = keyNode = valueNode = null; + isPair = isExplicitPair = false; + + if (ch === 0x3F/* ? */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following)) { + isPair = isExplicitPair = true; + state.position++; + skipSeparationSpace(state, true, nodeIndent); } + } - const style = - option === 'consistent' - ? entries[0] && (!entries[0].start || !entries[0].end) - ? 0 - : 1 - : option === 'padded' - ? 1 - : 0; + _line = state.line; // Save the current line. + _lineStart = state.lineStart; + _pos = state.position; + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + keyTag = state.tag; + keyNode = state.result; + skipSeparationSpace(state, true, nodeIndent); - index = -1; + ch = state.input.charCodeAt(state.position); - while (++index < entries.length) { - checkSide('start', entries[index], style, sizes); - checkSide('end', entries[index], style, sizes); - } + if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) { + isPair = true; + ch = state.input.charCodeAt(++state.position); + skipSeparationSpace(state, true, nodeIndent); + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + valueNode = state.result; + } - return SKIP$1 - }); + if (isMapping) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos); + } else if (isPair) { + _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos)); + } else { + _result.push(keyNode); + } - /** - * @param {'start'|'end'} side - * @param {Entry} entry - * @param {0|1} style - * @param {number[]} sizes - */ - function checkSide(side, entry, style, sizes) { - const cell = entry.node; - const column = entry.column; - const spacing = entry[side]; + skipSeparationSpace(state, true, nodeIndent); - if (spacing === undefined || spacing === style) { - return - } + ch = state.input.charCodeAt(state.position); - let reason = 'Cell should be '; + if (ch === 0x2C/* , */) { + readNext = true; + ch = state.input.charCodeAt(++state.position); + } else { + readNext = false; + } + } - if (style === 0) { - // Ignore every cell except the biggest in the column. - if (size$1(cell) < sizes[column]) { - return - } + throwError(state, 'unexpected end of the stream within a flow collection'); +} - reason += 'compact'; +function readBlockScalar(state, nodeIndent) { + var captureStart, + folding, + chomping = CHOMPING_CLIP, + didReadContent = false, + detectedIndent = false, + textIndent = nodeIndent, + emptyLines = 0, + atMoreIndented = false, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x7C/* | */) { + folding = false; + } else if (ch === 0x3E/* > */) { + folding = true; + } else { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + + while (ch !== 0) { + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + if (CHOMPING_CLIP === chomping) { + chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP; } else { - reason += 'padded'; + throwError(state, 'repeat of a chomping mode identifier'); + } - if (spacing > style) { - // May be right or center aligned. - if (size$1(cell) < sizes[column]) { - return - } + } else if ((tmp = fromDecimalCode(ch)) >= 0) { + if (tmp === 0) { + throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one'); + } else if (!detectedIndent) { + textIndent = nodeIndent + tmp - 1; + detectedIndent = true; + } else { + throwError(state, 'repeat of an indentation width identifier'); + } + + } else { + break; + } + } + + if (is_WHITE_SPACE(ch)) { + do { ch = state.input.charCodeAt(++state.position); } + while (is_WHITE_SPACE(ch)); + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (!is_EOL(ch) && (ch !== 0)); + } + } + + while (ch !== 0) { + readLineBreak(state); + state.lineIndent = 0; + + ch = state.input.charCodeAt(state.position); + + while ((!detectedIndent || state.lineIndent < textIndent) && + (ch === 0x20/* Space */)) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + + if (!detectedIndent && state.lineIndent > textIndent) { + textIndent = state.lineIndent; + } + + if (is_EOL(ch)) { + emptyLines++; + continue; + } - reason += ' with 1 space, not ' + spacing; + // End of the scalar. + if (state.lineIndent < textIndent) { + + // Perform the chomping. + if (chomping === CHOMPING_KEEP) { + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } else if (chomping === CHOMPING_CLIP) { + if (didReadContent) { // i.e. only if the scalar is not empty. + state.result += '\n'; } } - /** @type {Point} */ - let point; + // Break this `while` cycle and go to the funciton's epilogue. + break; + } - if (side === 'start') { - point = pointStart(cell); - if (!column) { - point.column++; + // Folded style: use fancy rules to handle line breaks. + if (folding) { - if (typeof point.offset === 'number') { - point.offset++; - } - } - } else { - point = pointEnd(cell); - point.column--; + // Lines starting with white space characters (more-indented lines) are not folded. + if (is_WHITE_SPACE(ch)) { + atMoreIndented = true; + // except for the first content line (cf. Example 8.1) + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); - if (typeof point.offset === 'number') { - point.offset--; + // End of more-indented block. + } else if (atMoreIndented) { + atMoreIndented = false; + state.result += common.repeat('\n', emptyLines + 1); + + // Just one line break - perceive as the same line. + } else if (emptyLines === 0) { + if (didReadContent) { // i.e. only if we have already read some scalar content. + state.result += ' '; } + + // Several line breaks - perceive as different lines. + } else { + state.result += common.repeat('\n', emptyLines); } - file.message(reason, point); + // Literal style: just add exact number of line breaks between content lines. + } else { + // Keep all line breaks except the header line break. + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); } - } -); -var remarkLintTableCellPadding$1 = remarkLintTableCellPadding; + didReadContent = true; + detectedIndent = true; + emptyLines = 0; + captureStart = state.position; -/** - * @param {TableCell} node - * @returns {number} - */ -function size$1(node) { - const head = pointStart(node.children[0]).offset; - const tail = pointEnd(node.children[node.children.length - 1]).offset; - // Only called when we’re sure offsets exist. - /* c8 ignore next */ - return typeof head === 'number' && typeof tail === 'number' ? tail - head : 0 + while (!is_EOL(ch) && (ch !== 0)) { + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, state.position, false); + } + + return true; } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module table-pipes - * @fileoverview - * Warn when table rows are not fenced with pipes. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * creates fenced rows with initial and final pipes by default. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md", "gfm": true} - * - * | A | B | - * | ----- | ----- | - * | Alpha | Bravo | - * - * @example - * {"name": "not-ok.md", "label": "input", "gfm": true} - * - * A | B - * ----- | ----- - * Alpha | Bravo - * - * @example - * {"name": "not-ok.md", "label": "output", "gfm": true} - * - * 1:1: Missing initial pipe in table fence - * 1:10: Missing final pipe in table fence - * 3:1: Missing initial pipe in table fence - * 3:14: Missing final pipe in table fence - */ +function readBlockSequence(state, nodeIndent) { + var _line, + _tag = state.tag, + _anchor = state.anchor, + _result = [], + following, + detected = false, + ch; -const reasonStart = 'Missing initial pipe in table fence'; -const reasonEnd = 'Missing final pipe in table fence'; + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; -const remarkLintTablePipes = lintRule( - { - origin: 'remark-lint:table-pipes', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-table-pipes#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file) => { - const value = String(file); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } - visit$1(tree, 'table', (node) => { - let index = -1; + ch = state.input.charCodeAt(state.position); - while (++index < node.children.length) { - const row = node.children[index]; - const start = pointStart(row); - const end = pointEnd(row); + while (ch !== 0) { + if (state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } - if ( - typeof start.offset === 'number' && - value.charCodeAt(start.offset) !== 124 - ) { - file.message(reasonStart, start); - } + if (ch !== 0x2D/* - */) { + break; + } - if ( - typeof end.offset === 'number' && - value.charCodeAt(end.offset - 1) !== 124 - ) { - file.message(reasonEnd, end); - } - } - }); - } -); + following = state.input.charCodeAt(state.position + 1); -var remarkLintTablePipes$1 = remarkLintTablePipes; + if (!is_WS_OR_EOL(following)) { + break; + } -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module unordered-list-marker-style - * @fileoverview - * Warn when the list item marker style of unordered lists violate a given - * style. - * - * Options: `'consistent'`, `'-'`, `'*'`, or `'+'`, default: `'consistent'`. - * - * `'consistent'` detects the first used list style and warns when subsequent - * lists use different styles. - * - * ## Fix - * - * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) - * formats unordered lists using `-` (hyphen-minus) by default. - * Pass - * [`bullet: '*'` or `bullet: '+'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsbullet) - * to use `*` (asterisk) or `+` (plus sign) instead. - * - * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) - * on how to automatically fix warnings for this rule. - * - * @example - * {"name": "ok.md"} - * - * By default (`'consistent'`), if the file uses only one marker, - * that’s OK. - * - * * Foo - * * Bar - * * Baz - * - * Ordered lists are not affected. - * - * 1. Foo - * 2. Bar - * 3. Baz - * - * @example - * {"name": "ok.md", "setting": "*"} - * - * * Foo - * - * @example - * {"name": "ok.md", "setting": "-"} - * - * - Foo - * - * @example - * {"name": "ok.md", "setting": "+"} - * - * + Foo - * - * @example - * {"name": "not-ok.md", "label": "input"} - * - * * Foo - * - Bar - * + Baz - * - * @example - * {"name": "not-ok.md", "label": "output"} - * - * 2:1-2:6: Marker style should be `*` - * 3:1-3:6: Marker style should be `*` - * - * @example - * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} - * - * 1:1: Incorrect unordered list item marker style `💩`: use either `'-'`, `'*'`, or `'+'` - */ + detected = true; + state.position++; + + if (skipSeparationSpace(state, true, -1)) { + if (state.lineIndent <= nodeIndent) { + _result.push(null); + ch = state.input.charCodeAt(state.position); + continue; + } + } -const markers = new Set(['-', '*', '+']); + _line = state.line; + composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true); + _result.push(state.result); + skipSeparationSpace(state, true, -1); -const remarkLintUnorderedListMarkerStyle = lintRule( - { - origin: 'remark-lint:unordered-list-marker-style', - url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-unordered-list-marker-style#readme' - }, - /** @type {import('unified-lint-rule').Rule} */ - (tree, file, option = 'consistent') => { - const value = String(file); + ch = state.input.charCodeAt(state.position); - if (option !== 'consistent' && !markers.has(option)) { - file.fail( - 'Incorrect unordered list item marker style `' + - option + - "`: use either `'-'`, `'*'`, or `'+'`" - ); + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a sequence entry'); + } else if (state.lineIndent < nodeIndent) { + break; } + } - visit$1(tree, 'list', (node) => { - if (node.ordered) return - - let index = -1; + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'sequence'; + state.result = _result; + return true; + } + return false; +} - while (++index < node.children.length) { - const child = node.children[index]; +function readBlockMapping(state, nodeIndent, flowIndent) { + var following, + allowCompact, + _line, + _keyLine, + _keyLineStart, + _keyPos, + _tag = state.tag, + _anchor = state.anchor, + _result = {}, + overridableKeys = Object.create(null), + keyTag = null, + keyNode = null, + valueNode = null, + atExplicitKey = false, + detected = false, + ch; - if (!generated(child)) { - const marker = /** @type {Marker} */ ( - value - .slice( - pointStart(child).offset, - pointStart(child.children[0]).offset - ) - .replace(/\[[x ]?]\s*$/i, '') - .replace(/\s/g, '') - ); + // there is a leading tab before this token, so it can't be a block sequence/mapping; + // it can still be flow sequence/mapping or a scalar + if (state.firstTabInLine !== -1) return false; - if (option === 'consistent') { - option = marker; - } else if (marker !== option) { - file.message('Marker style should be `' + option + '`', child); - } - } - } - }); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; } -); -var remarkLintUnorderedListMarkerStyle$1 = remarkLintUnorderedListMarkerStyle; + ch = state.input.charCodeAt(state.position); -// @see https://github.com/nodejs/node/blob/HEAD/doc/guides/doc-style-guide.md + while (ch !== 0) { + if (!atExplicitKey && state.firstTabInLine !== -1) { + state.position = state.firstTabInLine; + throwError(state, 'tab characters must not be used in indentation'); + } -// Add in rules alphabetically -const plugins = [ - // Leave preset at the top so it can be overridden - remarkPresetLintRecommended$1, - [remarkLintBlockquoteIndentation$1, 2], - [remarkLintCheckboxCharacterStyle$1, { checked: "x", unchecked: " " }], - remarkLintCheckboxContentIndent$1, - [remarkLintCodeBlockStyle$1, "fenced"], - remarkLintDefinitionSpacing$1, - [ - remarkLintFencedCodeFlag$1, - { - flags: [ - "bash", - "c", - "cjs", - "coffee", - "console", - "cpp", - "diff", - "http", - "js", - "json", - "markdown", - "mjs", - "powershell", - "r", - "text", - ], - }, - ], - [remarkLintFencedCodeMarker$1, "`"], - [remarkLintFileExtension$1, "md"], - remarkLintFinalDefinition$1, - [remarkLintFirstHeadingLevel$1, 1], - [remarkLintHeadingStyle$1, "atx"], - [remarkLintListItemIndent$1, "space"], - remarkLintMaximumLineLength$1, - remarkLintNoConsecutiveBlankLines$1, - remarkLintNoFileNameArticles$1, - remarkLintNoFileNameConsecutiveDashes$1, - remarkLintNofileNameOuterDashes$1, - remarkLintNoHeadingIndent$1, - remarkLintNoMultipleToplevelHeadings$1, - remarkLintNoShellDollars$1, - remarkLintNoTableIndentation$1, - remarkLintNoTabs$1, - remarkLintNoTrailingSpaces, - remarkLintNodejsLinks, - remarkLintNodejsYamlComments, - [ - remarkLintProhibitedStrings, - [ - { yes: "End-of-Life" }, - { yes: "GitHub" }, - { no: "hostname", yes: "host name" }, - { yes: "JavaScript" }, - { no: "[Ll]ong[ -][Tt]erm [Ss]upport", yes: "Long Term Support" }, - { no: "Node", yes: "Node.js", ignoreNextTo: "-API" }, - { yes: "Node.js" }, - { no: "Node[Jj][Ss]", yes: "Node.js" }, - { no: "Node\\.js's?", yes: "the Node.js" }, - { no: "[Nn]ote that", yes: "" }, - { yes: "RFC" }, - { no: "[Rr][Ff][Cc]\\d+", yes: "RFC " }, - { yes: "Unix" }, - { yes: "V8" }, - ], - ], - remarkLintRuleStyle$1, - [remarkLintStrongMarker$1, "*"], - [remarkLintTableCellPadding$1, "padded"], - remarkLintTablePipes$1, - [remarkLintUnorderedListMarkerStyle$1, "*"], -]; + following = state.input.charCodeAt(state.position + 1); + _line = state.line; // Save the current line. -const settings = { - emphasis: "_", - listItemIndent: 1, - tightDefinitions: true, -}; + // + // Explicit notation case. There are two separate blocks: + // first for the key (denoted by "?") and second for the value (denoted by ":") + // + if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) { -const remarkPresetLintNode = { plugins, settings }; + if (ch === 0x3F/* ? */) { + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } -/** - * @typedef {import('micromark-util-types').Extension} Extension - * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord - * @typedef {import('micromark-util-types').Tokenizer} Tokenizer - * @typedef {import('micromark-util-types').Previous} Previous - * @typedef {import('micromark-util-types').State} State - * @typedef {import('micromark-util-types').Event} Event - * @typedef {import('micromark-util-types').Code} Code - */ -const www = { - tokenize: tokenizeWww, - partial: true -}; -const domain = { - tokenize: tokenizeDomain, - partial: true -}; -const path = { - tokenize: tokenizePath, - partial: true -}; -const punctuation = { - tokenize: tokenizePunctuation, - partial: true -}; -const namedCharacterReference = { - tokenize: tokenizeNamedCharacterReference, - partial: true -}; -const wwwAutolink = { - tokenize: tokenizeWwwAutolink, - previous: previousWww -}; -const httpAutolink = { - tokenize: tokenizeHttpAutolink, - previous: previousHttp -}; -const emailAutolink = { - tokenize: tokenizeEmailAutolink, - previous: previousEmail -}; -/** @type {ConstructRecord} */ + detected = true; + atExplicitKey = true; + allowCompact = true; -const text = {}; -/** @type {Extension} */ + } else if (atExplicitKey) { + // i.e. 0x3A/* : */ === character after the explicit key. + atExplicitKey = false; + allowCompact = true; -const gfmAutolinkLiteral = { - text -}; -let code = 48; // Add alphanumerics. + } else { + throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line'); + } -while (code < 123) { - text[code] = emailAutolink; - code++; - if (code === 58) code = 65; - else if (code === 91) code = 97; -} + state.position += 1; + ch = following; + + // + // Implicit notation case. Flow-style node as the key first, then ":", and the value. + // + } else { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; -text[43] = emailAutolink; -text[45] = emailAutolink; -text[46] = emailAutolink; -text[95] = emailAutolink; -text[72] = [emailAutolink, httpAutolink]; -text[104] = [emailAutolink, httpAutolink]; -text[87] = [emailAutolink, wwwAutolink]; -text[119] = [emailAutolink, wwwAutolink]; -/** @type {Tokenizer} */ + if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) { + // Neither implicit nor explicit notation. + // Reading is done. Go to the epilogue. + break; + } -function tokenizeEmailAutolink(effects, ok, nok) { - const self = this; - /** @type {boolean} */ + if (state.line === _line) { + ch = state.input.charCodeAt(state.position); - let hasDot; - /** @type {boolean|undefined} */ + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } - let hasDigitInLastSegment; - return start - /** @type {State} */ + if (ch === 0x3A/* : */) { + ch = state.input.charCodeAt(++state.position); - function start(code) { - if ( - !gfmAtext(code) || - !previousEmail(self.previous) || - previousUnbalanced(self.events) - ) { - return nok(code) - } + if (!is_WS_OR_EOL(ch)) { + throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping'); + } - effects.enter('literalAutolink'); - effects.enter('literalAutolinkEmail'); - return atext(code) - } - /** @type {State} */ + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } - function atext(code) { - if (gfmAtext(code)) { - effects.consume(code); - return atext - } + detected = true; + atExplicitKey = false; + allowCompact = false; + keyTag = state.tag; + keyNode = state.result; - if (code === 64) { - effects.consume(code); - return label - } + } else if (detected) { + throwError(state, 'can not read an implicit mapping pair; a colon is missed'); - return nok(code) - } - /** @type {State} */ + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } - function label(code) { - if (code === 46) { - return effects.check(punctuation, done, dotContinuation)(code) - } + } else if (detected) { + throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key'); - if (code === 45 || code === 95) { - return effects.check(punctuation, nok, dashOrUnderscoreContinuation)(code) + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } } - if (asciiAlphanumeric(code)) { - if (!hasDigitInLastSegment && asciiDigit(code)) { - hasDigitInLastSegment = true; + // + // Common reading code for both explicit and implicit notations. + // + if (state.line === _line || state.lineIndent > nodeIndent) { + if (atExplicitKey) { + _keyLine = state.line; + _keyLineStart = state.lineStart; + _keyPos = state.position; } - effects.consume(code); - return label + if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { + if (atExplicitKey) { + keyNode = state.result; + } else { + valueNode = state.result; + } + } + + if (!atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos); + keyTag = keyNode = valueNode = null; + } + + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); } - return done(code) + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a mapping entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } } - /** @type {State} */ - function dotContinuation(code) { - effects.consume(code); - hasDot = true; - hasDigitInLastSegment = undefined; - return label + // + // Epilogue. + // + + // Special case: last mapping's node contains only the key in explicit notation. + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos); } - /** @type {State} */ - function dashOrUnderscoreContinuation(code) { - effects.consume(code); - return afterDashOrUnderscore + // Expose the resulting mapping. + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'mapping'; + state.result = _result; } - /** @type {State} */ - function afterDashOrUnderscore(code) { - if (code === 46) { - return effects.check(punctuation, nok, dotContinuation)(code) - } + return detected; +} - return label(code) - } - /** @type {State} */ +function readTagProperty(state) { + var _position, + isVerbatim = false, + isNamed = false, + tagHandle, + tagName, + ch; - function done(code) { - if (hasDot && !hasDigitInLastSegment) { - effects.exit('literalAutolinkEmail'); - effects.exit('literalAutolink'); - return ok(code) - } + ch = state.input.charCodeAt(state.position); - return nok(code) + if (ch !== 0x21/* ! */) return false; + + if (state.tag !== null) { + throwError(state, 'duplication of a tag property'); } -} -/** @type {Tokenizer} */ -function tokenizeWwwAutolink(effects, ok, nok) { - const self = this; - return start - /** @type {State} */ + ch = state.input.charCodeAt(++state.position); - function start(code) { - if ( - (code !== 87 && code !== 119) || - !previousWww(self.previous) || - previousUnbalanced(self.events) - ) { - return nok(code) - } + if (ch === 0x3C/* < */) { + isVerbatim = true; + ch = state.input.charCodeAt(++state.position); - effects.enter('literalAutolink'); - effects.enter('literalAutolinkWww'); // For `www.` we check instead of attempt, because when it matches, GH - // treats it as part of a domain (yes, it says a valid domain must come - // after `www.`, but that’s not how it’s implemented by them). + } else if (ch === 0x21/* ! */) { + isNamed = true; + tagHandle = '!!'; + ch = state.input.charCodeAt(++state.position); - return effects.check( - www, - effects.attempt(domain, effects.attempt(path, done), nok), - nok - )(code) + } else { + tagHandle = '!'; } - /** @type {State} */ - function done(code) { - effects.exit('literalAutolinkWww'); - effects.exit('literalAutolink'); - return ok(code) - } -} -/** @type {Tokenizer} */ + _position = state.position; -function tokenizeHttpAutolink(effects, ok, nok) { - const self = this; - return start - /** @type {State} */ + if (isVerbatim) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && ch !== 0x3E/* > */); - function start(code) { - if ( - (code !== 72 && code !== 104) || - !previousHttp(self.previous) || - previousUnbalanced(self.events) - ) { - return nok(code) + if (state.position < state.length) { + tagName = state.input.slice(_position, state.position); + ch = state.input.charCodeAt(++state.position); + } else { + throwError(state, 'unexpected end of the stream within a verbatim tag'); } + } else { + while (ch !== 0 && !is_WS_OR_EOL(ch)) { - effects.enter('literalAutolink'); - effects.enter('literalAutolinkHttp'); - effects.consume(code); - return t1 - } - /** @type {State} */ + if (ch === 0x21/* ! */) { + if (!isNamed) { + tagHandle = state.input.slice(_position - 1, state.position + 1); + + if (!PATTERN_TAG_HANDLE.test(tagHandle)) { + throwError(state, 'named tag handle cannot contain such characters'); + } + + isNamed = true; + _position = state.position + 1; + } else { + throwError(state, 'tag suffix cannot contain exclamation marks'); + } + } - function t1(code) { - if (code === 84 || code === 116) { - effects.consume(code); - return t2 + ch = state.input.charCodeAt(++state.position); } - return nok(code) - } - /** @type {State} */ + tagName = state.input.slice(_position, state.position); - function t2(code) { - if (code === 84 || code === 116) { - effects.consume(code); - return p + if (PATTERN_FLOW_INDICATORS.test(tagName)) { + throwError(state, 'tag suffix cannot contain flow indicator characters'); } - - return nok(code) } - /** @type {State} */ - function p(code) { - if (code === 80 || code === 112) { - effects.consume(code); - return s - } + if (tagName && !PATTERN_TAG_URI.test(tagName)) { + throwError(state, 'tag name cannot contain such characters: ' + tagName); + } - return nok(code) + try { + tagName = decodeURIComponent(tagName); + } catch (err) { + throwError(state, 'tag name is malformed: ' + tagName); } - /** @type {State} */ - function s(code) { - if (code === 83 || code === 115) { - effects.consume(code); - return colon - } + if (isVerbatim) { + state.tag = tagName; - return colon(code) - } - /** @type {State} */ + } else if (_hasOwnProperty$1.call(state.tagMap, tagHandle)) { + state.tag = state.tagMap[tagHandle] + tagName; - function colon(code) { - if (code === 58) { - effects.consume(code); - return slash1 - } + } else if (tagHandle === '!') { + state.tag = '!' + tagName; - return nok(code) + } else if (tagHandle === '!!') { + state.tag = 'tag:yaml.org,2002:' + tagName; + + } else { + throwError(state, 'undeclared tag handle "' + tagHandle + '"'); } - /** @type {State} */ - function slash1(code) { - if (code === 47) { - effects.consume(code); - return slash2 - } + return true; +} - return nok(code) - } - /** @type {State} */ +function readAnchorProperty(state) { + var _position, + ch; - function slash2(code) { - if (code === 47) { - effects.consume(code); - return after - } + ch = state.input.charCodeAt(state.position); - return nok(code) + if (ch !== 0x26/* & */) return false; + + if (state.anchor !== null) { + throwError(state, 'duplication of an anchor property'); } - /** @type {State} */ - function after(code) { - return code === null || - asciiControl(code) || - unicodeWhitespace(code) || - unicodePunctuation(code) - ? nok(code) - : effects.attempt(domain, effects.attempt(path, done), nok)(code) + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); } - /** @type {State} */ - function done(code) { - effects.exit('literalAutolinkHttp'); - effects.exit('literalAutolink'); - return ok(code) + if (state.position === _position) { + throwError(state, 'name of an anchor node must contain at least one character'); } + + state.anchor = state.input.slice(_position, state.position); + return true; } -/** @type {Tokenizer} */ -function tokenizeWww(effects, ok, nok) { - return start - /** @type {State} */ +function readAlias(state) { + var _position, alias, + ch; - function start(code) { - effects.consume(code); - return w2 - } - /** @type {State} */ + ch = state.input.charCodeAt(state.position); - function w2(code) { - if (code === 87 || code === 119) { - effects.consume(code); - return w3 - } + if (ch !== 0x2A/* * */) return false; - return nok(code) - } - /** @type {State} */ + ch = state.input.charCodeAt(++state.position); + _position = state.position; - function w3(code) { - if (code === 87 || code === 119) { - effects.consume(code); - return dot - } + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } - return nok(code) + if (state.position === _position) { + throwError(state, 'name of an alias node must contain at least one character'); } - /** @type {State} */ - function dot(code) { - if (code === 46) { - effects.consume(code); - return after - } + alias = state.input.slice(_position, state.position); - return nok(code) + if (!_hasOwnProperty$1.call(state.anchorMap, alias)) { + throwError(state, 'unidentified alias "' + alias + '"'); } - /** @type {State} */ - function after(code) { - return code === null || markdownLineEnding(code) ? nok(code) : ok(code) - } + state.result = state.anchorMap[alias]; + skipSeparationSpace(state, true, -1); + return true; } -/** @type {Tokenizer} */ -function tokenizeDomain(effects, ok, nok) { - /** @type {boolean|undefined} */ - let hasUnderscoreInLastSegment; - /** @type {boolean|undefined} */ +function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { + var allowBlockStyles, + allowBlockScalars, + allowBlockCollections, + indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } } + } - if (code === 46 || code === 95) { - return effects.check(punctuation, done, punctuationContinuation)(code) - } // GH documents that only alphanumerics (other than `-`, `.`, and `_`) can - // occur, which sounds like ASCII only, but they also support `www.點看.com`, - // so that’s Unicode. - // Instead of some new production for Unicode alphanumerics, markdown - // already has that for Unicode punctuation and whitespace, so use those. + if (indentStatus === 1) { + while (readTagProperty(state) || readAnchorProperty(state)) { + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + allowBlockCollections = allowBlockStyles; - if ( - code === null || - asciiControl(code) || - unicodeWhitespace(code) || - (code !== 45 && unicodePunctuation(code)) - ) { - return done(code) + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } else { + allowBlockCollections = false; + } } + } - effects.consume(code); - return domain + if (allowBlockCollections) { + allowBlockCollections = atNewLine || allowCompact; } - /** @type {State} */ - function punctuationContinuation(code) { - if (code === 46) { - hasUnderscoreInLastLastSegment = hasUnderscoreInLastSegment; - hasUnderscoreInLastSegment = undefined; - effects.consume(code); - return domain + if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) { + if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) { + flowIndent = parentIndent; + } else { + flowIndent = parentIndent + 1; } - if (code === 95) hasUnderscoreInLastSegment = true; - effects.consume(code); - return domain - } - /** @type {State} */ + blockIndent = state.position - state.lineStart; - function done(code) { - if (!hasUnderscoreInLastLastSegment && !hasUnderscoreInLastSegment) { - return ok(code) - } + if (indentStatus === 1) { + if (allowBlockCollections && + (readBlockSequence(state, blockIndent) || + readBlockMapping(state, blockIndent, flowIndent)) || + readFlowCollection(state, flowIndent)) { + hasContent = true; + } else { + if ((allowBlockScalars && readBlockScalar(state, flowIndent)) || + readSingleQuotedScalar(state, flowIndent) || + readDoubleQuotedScalar(state, flowIndent)) { + hasContent = true; - return nok(code) - } -} -/** @type {Tokenizer} */ + } else if (readAlias(state)) { + hasContent = true; -function tokenizePath(effects, ok) { - let balance = 0; - return inPath - /** @type {State} */ + if (state.tag !== null || state.anchor !== null) { + throwError(state, 'alias node should not have any properties'); + } - function inPath(code) { - if (code === 38) { - return effects.check( - namedCharacterReference, - ok, - continuedPunctuation - )(code) - } + } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) { + hasContent = true; - if (code === 40) { - balance++; - } + if (state.tag === null) { + state.tag = '?'; + } + } - if (code === 41) { - return effects.check( - punctuation, - parenAtPathEnd, - continuedPunctuation - )(code) + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } else if (indentStatus === 0) { + // Special case: block sequences are allowed to have same indentation level as the parent. + // http://www.yaml.org/spec/1.2/spec.html#id2799784 + hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); } + } - if (pathEnd(code)) { - return ok(code) + if (state.tag === null) { + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; } - if (trailingPunctuation(code)) { - return effects.check(punctuation, ok, continuedPunctuation)(code) + } else if (state.tag === '?') { + // Implicit resolving is not allowed for non-scalar types, and '?' + // non-specific tag is only automatically assigned to plain scalars. + // + // We only need to check kind conformity in case user explicitly assigns '?' + // tag, for example like this: "! [0]" + // + if (state.result !== null && state.kind !== 'scalar') { + throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); } - effects.consume(code); - return inPath - } - /** @type {State} */ - - function continuedPunctuation(code) { - effects.consume(code); - return inPath - } - /** @type {State} */ - - function parenAtPathEnd(code) { - balance--; - return balance < 0 ? ok(code) : continuedPunctuation(code) - } -} -/** @type {Tokenizer} */ + for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { + type = state.implicitTypes[typeIndex]; -function tokenizeNamedCharacterReference(effects, ok, nok) { - return start - /** @type {State} */ + if (type.resolve(state.result)) { // `state.result` updated in resolver if matched + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + break; + } + } + } else if (state.tag !== '!') { + if (_hasOwnProperty$1.call(state.typeMap[state.kind || 'fallback'], state.tag)) { + type = state.typeMap[state.kind || 'fallback'][state.tag]; + } else { + // looking for multi type + type = null; + typeList = state.typeMap.multi[state.kind || 'fallback']; - function start(code) { - effects.consume(code); - return inside - } - /** @type {State} */ + for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) { + if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) { + type = typeList[typeIndex]; + break; + } + } + } - function inside(code) { - if (asciiAlpha(code)) { - effects.consume(code); - return inside + if (!type) { + throwError(state, 'unknown tag !<' + state.tag + '>'); } - if (code === 59) { - effects.consume(code); - return after + if (state.result !== null && type.kind !== state.kind) { + throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); } - return nok(code) + if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched + throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); + } else { + state.result = type.construct(state.result, state.tag); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } } - /** @type {State} */ - function after(code) { - // If the named character reference is followed by the end of the path, it’s - // not continued punctuation. - return pathEnd(code) ? ok(code) : nok(code) + if (state.listener !== null) { + state.listener('close', state); } + return state.tag !== null || state.anchor !== null || hasContent; } -/** @type {Tokenizer} */ -function tokenizePunctuation(effects, ok, nok) { - return start - /** @type {State} */ +function readDocument(state) { + var documentStart = state.position, + _position, + directiveName, + directiveArgs, + hasDirectives = false, + ch; - function start(code) { - effects.consume(code); - return after - } - /** @type {State} */ + state.version = null; + state.checkLineBreaks = state.legacy; + state.tagMap = Object.create(null); + state.anchorMap = Object.create(null); - function after(code) { - // Check the next. - if (trailingPunctuation(code)) { - effects.consume(code); - return after - } // If the punctuation marker is followed by the end of the path, it’s not - // continued punctuation. + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + skipSeparationSpace(state, true, -1); - return pathEnd(code) ? ok(code) : nok(code) - } -} -/** - * @param {Code} code - * @returns {boolean} - */ + ch = state.input.charCodeAt(state.position); -function trailingPunctuation(code) { - return ( - code === 33 || - code === 34 || - code === 39 || - code === 41 || - code === 42 || - code === 44 || - code === 46 || - code === 58 || - code === 59 || - code === 60 || - code === 63 || - code === 95 || - code === 126 - ) -} -/** - * @param {Code} code - * @returns {boolean} - */ + if (state.lineIndent > 0 || ch !== 0x25/* % */) { + break; + } -function pathEnd(code) { - return code === null || code === 60 || markdownLineEndingOrSpace(code) -} -/** - * @param {Code} code - * @returns {boolean} - */ + hasDirectives = true; + ch = state.input.charCodeAt(++state.position); + _position = state.position; -function gfmAtext(code) { - return ( - code === 43 || - code === 45 || - code === 46 || - code === 95 || - asciiAlphanumeric(code) - ) -} -/** @type {Previous} */ + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } -function previousWww(code) { - return ( - code === null || - code === 40 || - code === 42 || - code === 95 || - code === 126 || - markdownLineEndingOrSpace(code) - ) -} -/** @type {Previous} */ + directiveName = state.input.slice(_position, state.position); + directiveArgs = []; -function previousHttp(code) { - return code === null || !asciiAlpha(code) -} -/** @type {Previous} */ + if (directiveName.length < 1) { + throwError(state, 'directive name must not be less than one character in length'); + } -function previousEmail(code) { - return code !== 47 && previousHttp(code) -} -/** - * @param {Event[]} events - * @returns {boolean} - */ + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } -function previousUnbalanced(events) { - let index = events.length; - let result = false; + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && !is_EOL(ch)); + break; + } - while (index--) { - const token = events[index][1]; + if (is_EOL(ch)) break; - if ( - (token.type === 'labelLink' || token.type === 'labelImage') && - !token._balanced - ) { - result = true; - break - } // @ts-expect-error If we’ve seen this token, and it was marked as not - // having any unbalanced bracket before it, we can exit. + _position = state.position; - if (token._gfmAutolinkLiteralWalkedInto) { - result = false; - break + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveArgs.push(state.input.slice(_position, state.position)); } - } - if (events.length > 0 && !result) { - // @ts-expect-error Mark the last token as “walked into” w/o finding - // anything. - events[events.length - 1][1]._gfmAutolinkLiteralWalkedInto = true; - } + if (ch !== 0) readLineBreak(state); - return result -} + if (_hasOwnProperty$1.call(directiveHandlers, directiveName)) { + directiveHandlers[directiveName](state, directiveName, directiveArgs); + } else { + throwWarning(state, 'unknown document directive "' + directiveName + '"'); + } + } -const characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'}; + skipSeparationSpace(state, true, -1); -/** - * Encode only the dangerous HTML characters. - * - * This ensures that certain characters which have special meaning in HTML are - * dealt with. - * Technically, we can skip `>` and `"` in many cases, but CM includes them. - * - * @param {string} value - * @returns {string} - */ -function encode(value) { - return value.replace(/["&<>]/g, replace) + if (state.lineIndent === 0 && + state.input.charCodeAt(state.position) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 1) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 2) === 0x2D/* - */) { + state.position += 3; + skipSeparationSpace(state, true, -1); - /** - * @param {string} value - * @returns {string} - */ - function replace(value) { - // @ts-expect-error Hush, it’s fine. - return '&' + characterReferences[value] + ';' + } else if (hasDirectives) { + throwError(state, 'directives end mark is expected'); } -} -/** - * Make a value safe for injection as a URL. - * - * This encodes unsafe characters with percent-encoding and skips already - * encoded sequences (see `normalizeUri` below). - * Further unsafe characters are encoded as character references (see - * `micromark-util-encode`). - * - * Then, a regex of allowed protocols can be given, in which case the URL is - * sanitized. - * For example, `/^(https?|ircs?|mailto|xmpp)$/i` can be used for `a[href]`, - * or `/^https?$/i` for `img[src]`. - * If the URL includes an unknown protocol (one not matched by `protocol`, such - * as a dangerous example, `javascript:`), the value is ignored. - * - * @param {string|undefined} url - * @param {RegExp} [protocol] - * @returns {string} - */ -function sanitizeUri(url, protocol) { - const value = encode(normalizeUri(url || '')); + composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); + skipSeparationSpace(state, true, -1); - if (!protocol) { - return value + if (state.checkLineBreaks && + PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { + throwWarning(state, 'non-ASCII line breaks are interpreted as content'); } - const colon = value.indexOf(':'); - const questionMark = value.indexOf('?'); - const numberSign = value.indexOf('#'); - const slash = value.indexOf('/'); + state.documents.push(state.result); - if ( - // If there is no protocol, it’s relative. - colon < 0 || // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol. - (slash > -1 && colon > slash) || - (questionMark > -1 && colon > questionMark) || - (numberSign > -1 && colon > numberSign) || // It is a protocol, it should be allowed. - protocol.test(value.slice(0, colon)) - ) { - return value + if (state.position === state.lineStart && testDocumentSeparator(state)) { + + if (state.input.charCodeAt(state.position) === 0x2E/* . */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } + return; } - return '' + if (state.position < (state.length - 1)) { + throwError(state, 'end of the stream or a document separator is expected'); + } else { + return; + } } -/** - * Normalize a URL (such as used in definitions). - * - * Encode unsafe characters with percent-encoding, skipping already encoded - * sequences. - * - * @param {string} value - * @returns {string} - */ - -function normalizeUri(value) { - /** @type {string[]} */ - const result = []; - let index = -1; - let start = 0; - let skip = 0; - - while (++index < value.length) { - const code = value.charCodeAt(index); - /** @type {string} */ - let replace = ''; // A correct percent encoded value. - if ( - code === 37 && - asciiAlphanumeric(value.charCodeAt(index + 1)) && - asciiAlphanumeric(value.charCodeAt(index + 2)) - ) { - skip = 2; - } // ASCII. - else if (code < 128) { - if (!/[!#$&-;=?-Z_a-z~]/.test(String.fromCharCode(code))) { - replace = String.fromCharCode(code); - } - } // Astral. - else if (code > 55295 && code < 57344) { - const next = value.charCodeAt(index + 1); // A correct surrogate pair. +function loadDocuments(input, options) { + input = String(input); + options = options || {}; - if (code < 56320 && next > 56319 && next < 57344) { - replace = String.fromCharCode(code, next); - skip = 1; - } // Lone surrogate. - else { - replace = '\uFFFD'; - } - } // Unicode. - else { - replace = String.fromCharCode(code); - } + if (input.length !== 0) { - if (replace) { - result.push(value.slice(start, index), encodeURIComponent(replace)); - start = index + skip + 1; - replace = ''; + // Add tailing `\n` if not exists + if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ && + input.charCodeAt(input.length - 1) !== 0x0D/* CR */) { + input += '\n'; } - if (skip) { - index += skip; - skip = 0; + // Strip BOM + if (input.charCodeAt(0) === 0xFEFF) { + input = input.slice(1); } } - return result.join('') + value.slice(start) -} + var state = new State$1(input, options); -/** - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - * @typedef {import('micromark-util-types').Handle} Handle - * @typedef {import('micromark-util-types').CompileContext} CompileContext - * @typedef {import('micromark-util-types').Token} Token - */ -/** @type {HtmlExtension} */ + var nullpos = input.indexOf('\0'); -const gfmAutolinkLiteralHtml = { - exit: { - literalAutolinkEmail, - literalAutolinkHttp, - literalAutolinkWww + if (nullpos !== -1) { + state.position = nullpos; + throwError(state, 'null byte is not allowed in input'); } -}; -/** @type {Handle} */ -function literalAutolinkWww(token) { - anchorFromToken.call(this, token, 'http://'); -} -/** @type {Handle} */ + // Use 0 as string terminator. That significantly simplifies bounds check. + state.input += '\0'; -function literalAutolinkEmail(token) { - anchorFromToken.call(this, token, 'mailto:'); -} -/** @type {Handle} */ + while (state.input.charCodeAt(state.position) === 0x20/* Space */) { + state.lineIndent += 1; + state.position += 1; + } -function literalAutolinkHttp(token) { - anchorFromToken.call(this, token); -} -/** - * @this CompileContext - * @param {Token} token - * @param {string} [protocol] - * @returns {void} - */ + while (state.position < (state.length - 1)) { + readDocument(state); + } -function anchorFromToken(token, protocol) { - const url = this.sliceSerialize(token); - this.tag(''); - this.raw(this.encode(url)); - this.tag(''); + return state.documents; } -/** - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - */ -/** @type {HtmlExtension} */ -const gfmStrikethroughHtml = { - enter: { - strikethrough() { - this.tag(''); - } - }, - exit: { - strikethrough() { - this.tag(''); - } +function loadAll$1(input, iterator, options) { + if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') { + options = iterator; + iterator = null; } -}; -/** - * @typedef {import('micromark-util-types').Extension} Extension - * @typedef {import('micromark-util-types').Resolver} Resolver - * @typedef {import('micromark-util-types').Tokenizer} Tokenizer - * @typedef {import('micromark-util-types').State} State - * @typedef {import('micromark-util-types').Token} Token - * @typedef {import('micromark-util-types').Event} Event - */ + var documents = loadDocuments(input, options); -/** - * @param {Options} [options] - * @returns {Extension} - */ -function gfmStrikethrough(options = {}) { - let single = options.singleTilde; - const tokenizer = { - tokenize: tokenizeStrikethrough, - resolveAll: resolveAllStrikethrough - }; + if (typeof iterator !== 'function') { + return documents; + } - if (single === null || single === undefined) { - single = true; + for (var index = 0, length = documents.length; index < length; index += 1) { + iterator(documents[index]); } +} - return { - text: { - [126]: tokenizer - }, - insideSpan: { - null: [tokenizer] - }, - attentionMarkers: { - null: [126] - } + +function load$1(input, options) { + var documents = loadDocuments(input, options); + + if (documents.length === 0) { + /*eslint-disable no-undefined*/ + return undefined; + } else if (documents.length === 1) { + return documents[0]; } - /** - * Take events and resolve strikethrough. - * - * @type {Resolver} - */ + throw new exception('expected a single document in the stream, but found more'); +} - function resolveAllStrikethrough(events, context) { - let index = -1; - /** @type {Token} */ - let strikethrough; - /** @type {Token} */ +var loadAll_1 = loadAll$1; +var load_1 = load$1; - let text; - /** @type {number} */ +var loader = { + loadAll: loadAll_1, + load: load_1 +}; - let open; - /** @type {Event[]} */ +/*eslint-disable no-use-before-define*/ - let nextEvents; // Walk through all events. - while (++index < events.length) { - // Find a token that can close. - if ( - events[index][0] === 'enter' && - events[index][1].type === 'strikethroughSequenceTemporary' && - events[index][1]._close - ) { - open = index; // Now walk back to find an opener. - while (open--) { - // Find a token that can open the closer. - if ( - events[open][0] === 'exit' && - events[open][1].type === 'strikethroughSequenceTemporary' && - events[open][1]._open && // If the sizes are the same: - events[index][1].end.offset - events[index][1].start.offset === - events[open][1].end.offset - events[open][1].start.offset - ) { - events[index][1].type = 'strikethroughSequence'; - events[open][1].type = 'strikethroughSequence'; - strikethrough = { - type: 'strikethrough', - start: Object.assign({}, events[open][1].start), - end: Object.assign({}, events[index][1].end) - }; - text = { - type: 'strikethroughText', - start: Object.assign({}, events[open][1].end), - end: Object.assign({}, events[index][1].start) - }; // Opening. - nextEvents = [ - ['enter', strikethrough, context], - ['enter', events[open][1], context], - ['exit', events[open][1], context], - ['enter', text, context] - ]; // Between. - splice( - nextEvents, - nextEvents.length, - 0, - resolveAll( - context.parser.constructs.insideSpan.null, - events.slice(open + 1, index), - context - ) - ); // Closing. +var _toString = Object.prototype.toString; +var _hasOwnProperty = Object.prototype.hasOwnProperty; - splice(nextEvents, nextEvents.length, 0, [ - ['exit', text, context], - ['enter', events[index][1], context], - ['exit', events[index][1], context], - ['exit', strikethrough, context] - ]); - splice(events, open - 1, index - open + 3, nextEvents); - index = open + nextEvents.length - 2; - break - } - } - } - } +var CHAR_BOM = 0xFEFF; +var CHAR_TAB = 0x09; /* Tab */ +var CHAR_LINE_FEED = 0x0A; /* LF */ +var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ +var CHAR_SPACE = 0x20; /* Space */ +var CHAR_EXCLAMATION = 0x21; /* ! */ +var CHAR_DOUBLE_QUOTE = 0x22; /* " */ +var CHAR_SHARP = 0x23; /* # */ +var CHAR_PERCENT = 0x25; /* % */ +var CHAR_AMPERSAND = 0x26; /* & */ +var CHAR_SINGLE_QUOTE = 0x27; /* ' */ +var CHAR_ASTERISK = 0x2A; /* * */ +var CHAR_COMMA = 0x2C; /* , */ +var CHAR_MINUS = 0x2D; /* - */ +var CHAR_COLON = 0x3A; /* : */ +var CHAR_EQUALS = 0x3D; /* = */ +var CHAR_GREATER_THAN = 0x3E; /* > */ +var CHAR_QUESTION = 0x3F; /* ? */ +var CHAR_COMMERCIAL_AT = 0x40; /* @ */ +var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */ +var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */ +var CHAR_GRAVE_ACCENT = 0x60; /* ` */ +var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */ +var CHAR_VERTICAL_LINE = 0x7C; /* | */ +var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */ - index = -1; +var ESCAPE_SEQUENCES = {}; - while (++index < events.length) { - if (events[index][1].type === 'strikethroughSequenceTemporary') { - events[index][1].type = 'data'; - } - } +ESCAPE_SEQUENCES[0x00] = '\\0'; +ESCAPE_SEQUENCES[0x07] = '\\a'; +ESCAPE_SEQUENCES[0x08] = '\\b'; +ESCAPE_SEQUENCES[0x09] = '\\t'; +ESCAPE_SEQUENCES[0x0A] = '\\n'; +ESCAPE_SEQUENCES[0x0B] = '\\v'; +ESCAPE_SEQUENCES[0x0C] = '\\f'; +ESCAPE_SEQUENCES[0x0D] = '\\r'; +ESCAPE_SEQUENCES[0x1B] = '\\e'; +ESCAPE_SEQUENCES[0x22] = '\\"'; +ESCAPE_SEQUENCES[0x5C] = '\\\\'; +ESCAPE_SEQUENCES[0x85] = '\\N'; +ESCAPE_SEQUENCES[0xA0] = '\\_'; +ESCAPE_SEQUENCES[0x2028] = '\\L'; +ESCAPE_SEQUENCES[0x2029] = '\\P'; - return events - } - /** @type {Tokenizer} */ +var DEPRECATED_BOOLEANS_SYNTAX = [ + 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON', + 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF' +]; - function tokenizeStrikethrough(effects, ok, nok) { - const previous = this.previous; - const events = this.events; - let size = 0; - return start - /** @type {State} */ +var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/; - function start(code) { - if ( - code !== 126 || - (previous === 126 && - events[events.length - 1][1].type !== 'characterEscape') - ) { - return nok(code) - } +function compileStyleMap(schema, map) { + var result, keys, index, length, tag, style, type; - effects.enter('strikethroughSequenceTemporary'); - return more(code) - } - /** @type {State} */ + if (map === null) return {}; - function more(code) { - const before = classifyCharacter(previous); + result = {}; + keys = Object.keys(map); - if (code === 126) { - // If this is the third marker, exit. - if (size > 1) return nok(code) - effects.consume(code); - size++; - return more - } + for (index = 0, length = keys.length; index < length; index += 1) { + tag = keys[index]; + style = String(map[tag]); - if (size < 2 && !single) return nok(code) - const token = effects.exit('strikethroughSequenceTemporary'); - const after = classifyCharacter(code); - token._open = !after || (after === 2 && Boolean(before)); - token._close = !before || (before === 2 && Boolean(after)); - return ok(code) + if (tag.slice(0, 2) === '!!') { + tag = 'tag:yaml.org,2002:' + tag.slice(2); } + type = schema.compiledTypeMap['fallback'][tag]; + + if (type && _hasOwnProperty.call(type.styleAliases, style)) { + style = type.styleAliases[style]; + } + + result[tag] = style; } -} -/** - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - */ + return result; +} -/** - * @typedef {import('./syntax.js').Align} Align - */ -const alignment = { - null: '', - left: ' align="left"', - right: ' align="right"', - center: ' align="center"' -}; -/** @type {HtmlExtension} */ +function encodeHex(character) { + var string, handle, length; -const gfmTableHtml = { - enter: { - table(token) { - this.lineEndingIfNeeded(); - this.tag(''); // @ts-expect-error Custom. + string = character.toString(16).toUpperCase(); - this.setData('tableAlign', token._align); - }, + if (character <= 0xFF) { + handle = 'x'; + length = 2; + } else if (character <= 0xFFFF) { + handle = 'u'; + length = 4; + } else if (character <= 0xFFFFFFFF) { + handle = 'U'; + length = 8; + } else { + throw new exception('code point within a string may not be greater than 0xFFFFFFFF'); + } - tableBody() { - // Clear slurping line ending from the delimiter row. - this.setData('slurpOneLineEnding'); - this.tag(''); - }, + return '\\' + handle + common.repeat('0', length - string.length) + string; +} - tableData() { - /** @type {string|undefined} */ - const align = // @ts-expect-error Custom. - alignment[this.getData('tableAlign')[this.getData('tableColumn')]]; - if (align === undefined) { - // Capture results to ignore them. - this.buffer(); - } else { - this.lineEndingIfNeeded(); - this.tag(''); - } - }, +var QUOTING_TYPE_SINGLE = 1, + QUOTING_TYPE_DOUBLE = 2; - tableHead() { - this.lineEndingIfNeeded(); - this.tag(''); - }, +function State(options) { + this.schema = options['schema'] || _default; + this.indent = Math.max(1, (options['indent'] || 2)); + this.noArrayIndent = options['noArrayIndent'] || false; + this.skipInvalid = options['skipInvalid'] || false; + this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']); + this.styleMap = compileStyleMap(this.schema, options['styles'] || null); + this.sortKeys = options['sortKeys'] || false; + this.lineWidth = options['lineWidth'] || 80; + this.noRefs = options['noRefs'] || false; + this.noCompatMode = options['noCompatMode'] || false; + this.condenseFlow = options['condenseFlow'] || false; + this.quotingType = options['quotingType'] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE; + this.forceQuotes = options['forceQuotes'] || false; + this.replacer = typeof options['replacer'] === 'function' ? options['replacer'] : null; - tableHeader() { - this.lineEndingIfNeeded(); - this.tag( - '' - ); - }, + this.implicitTypes = this.schema.compiledImplicit; + this.explicitTypes = this.schema.compiledExplicit; - tableRow() { - this.setData('tableColumn', 0); - this.lineEndingIfNeeded(); - this.tag(''); - } - }, - exit: { - // Overwrite the default code text data handler to unescape escaped pipes when - // they are in tables. - codeTextData(token) { - let value = this.sliceSerialize(token); + this.tag = null; + this.result = ''; - if (this.getData('tableAlign')) { - value = value.replace(/\\([\\|])/g, replace$1); - } + this.duplicates = []; + this.usedDuplicates = null; +} - this.raw(this.encode(value)); - }, +// Indents every line in a string. Empty lines (\n only) are not indented. +function indentString(string, spaces) { + var ind = common.repeat(' ', spaces), + position = 0, + next = -1, + result = '', + line, + length = string.length; - table() { - this.setData('tableAlign'); // If there was no table body, make sure the slurping from the delimiter row - // is cleared. + while (position < length) { + next = string.indexOf('\n', position); + if (next === -1) { + line = string.slice(position); + position = length; + } else { + line = string.slice(position, next + 1); + position = next + 1; + } - this.setData('slurpAllLineEndings'); - this.lineEndingIfNeeded(); - this.tag('
'); - }, + if (line.length && line !== '\n') result += ind; - tableBody() { - this.lineEndingIfNeeded(); - this.tag(''); - }, + result += line; + } - tableData() { - /** @type {number} */ - // @ts-expect-error Custom. - const column = this.getData('tableColumn'); // @ts-expect-error Custom. + return result; +} - if (column in this.getData('tableAlign')) { - this.tag(''); - this.setData('tableColumn', column + 1); - } else { - // Stop capturing. - this.resume(); - } - }, +function generateNextLine(state, level) { + return '\n' + common.repeat(' ', state.indent * level); +} - tableHead() { - this.lineEndingIfNeeded(); - this.tag(''); - this.setData('slurpOneLineEnding', true); // Slurp the line ending from the delimiter row. - }, +function testImplicitResolving(state, str) { + var index, length, type; - tableHeader() { - this.tag(''); // @ts-expect-error Custom. + for (index = 0, length = state.implicitTypes.length; index < length; index += 1) { + type = state.implicitTypes[index]; - this.setData('tableColumn', this.getData('tableColumn') + 1); - }, + if (type.resolve(str)) { + return true; + } + } - tableRow() { - /** @type {Align[]} */ - // @ts-expect-error Custom. - const align = this.getData('tableAlign'); - /** @type {number} */ - // @ts-expect-error Custom. + return false; +} - let column = this.getData('tableColumn'); +// [33] s-white ::= s-space | s-tab +function isWhitespace(c) { + return c === CHAR_SPACE || c === CHAR_TAB; +} - while (column < align.length) { - this.lineEndingIfNeeded(); // @ts-expect-error `null` is fine as an index. +// Returns true if the character can be printed without escaping. +// From YAML 1.2: "any allowed characters known to be non-printable +// should also be escaped. [However,] This isn’t mandatory" +// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029. +function isPrintable(c) { + return (0x00020 <= c && c <= 0x00007E) + || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029) + || ((0x0E000 <= c && c <= 0x00FFFD) && c !== CHAR_BOM) + || (0x10000 <= c && c <= 0x10FFFF); +} - this.tag(''); - column++; - } +// [34] ns-char ::= nb-char - s-white +// [27] nb-char ::= c-printable - b-char - c-byte-order-mark +// [26] b-char ::= b-line-feed | b-carriage-return +// Including s-white (for some reason, examples doesn't match specs in this aspect) +// ns-char ::= c-printable - b-line-feed - b-carriage-return - c-byte-order-mark +function isNsCharOrWhitespace(c) { + return isPrintable(c) + && c !== CHAR_BOM + // - b-char + && c !== CHAR_CARRIAGE_RETURN + && c !== CHAR_LINE_FEED; +} - this.setData('tableColumn', column); - this.lineEndingIfNeeded(); - this.tag(''); - } - } -}; -/** - * @param {string} $0 - * @param {string} $1 - * @returns {string} - */ +// [127] ns-plain-safe(c) ::= c = flow-out ⇒ ns-plain-safe-out +// c = flow-in ⇒ ns-plain-safe-in +// c = block-key ⇒ ns-plain-safe-out +// c = flow-key ⇒ ns-plain-safe-in +// [128] ns-plain-safe-out ::= ns-char +// [129] ns-plain-safe-in ::= ns-char - c-flow-indicator +// [130] ns-plain-char(c) ::= ( ns-plain-safe(c) - “:” - “#” ) +// | ( /* An ns-char preceding */ “#” ) +// | ( “:” /* Followed by an ns-plain-safe(c) */ ) +function isPlainSafe(c, prev, inblock) { + var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c); + var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c); + return ( + // ns-plain-safe + inblock ? // c = flow-in + cIsNsCharOrWhitespace + : cIsNsCharOrWhitespace + // - c-flow-indicator + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + ) + // ns-plain-char + && c !== CHAR_SHARP // false on '#' + && !(prev === CHAR_COLON && !cIsNsChar) // false on ': ' + || (isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP) // change to true on '[^ ]#' + || (prev === CHAR_COLON && cIsNsChar); // change to true on ':[^ ]' +} -function replace$1($0, $1) { - // Pipes work, backslashes don’t (but can’t escape pipes). - return $1 === '|' ? $1 : $0 +// Simplified test for values allowed as the first character in plain style. +function isPlainSafeFirst(c) { + // Uses a subset of ns-char - c-indicator + // where ns-char = nb-char - s-white. + // No support of ( ( “?” | “:” | “-” ) /* Followed by an ns-plain-safe(c)) */ ) part + return isPrintable(c) && c !== CHAR_BOM + && !isWhitespace(c) // - s-white + // - (c-indicator ::= + // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}” + && c !== CHAR_MINUS + && c !== CHAR_QUESTION + && c !== CHAR_COLON + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + // | “#” | “&” | “*” | “!” | “|” | “=” | “>” | “'” | “"” + && c !== CHAR_SHARP + && c !== CHAR_AMPERSAND + && c !== CHAR_ASTERISK + && c !== CHAR_EXCLAMATION + && c !== CHAR_VERTICAL_LINE + && c !== CHAR_EQUALS + && c !== CHAR_GREATER_THAN + && c !== CHAR_SINGLE_QUOTE + && c !== CHAR_DOUBLE_QUOTE + // | “%” | “@” | “`”) + && c !== CHAR_PERCENT + && c !== CHAR_COMMERCIAL_AT + && c !== CHAR_GRAVE_ACCENT; } -/** - * @typedef {import('micromark-util-types').Extension} Extension - * @typedef {import('micromark-util-types').Resolver} Resolver - * @typedef {import('micromark-util-types').Tokenizer} Tokenizer - * @typedef {import('micromark-util-types').State} State - * @typedef {import('micromark-util-types').Token} Token - */ +// Simplified test for values allowed as the last character in plain style. +function isPlainSafeLast(c) { + // just not whitespace or colon, it will be checked to be plain character later + return !isWhitespace(c) && c !== CHAR_COLON; +} -/** @type {Extension} */ -const gfmTable = { - flow: { - null: { - tokenize: tokenizeTable, - resolve: resolveTable +// Same as 'string'.codePointAt(pos), but works in older browsers. +function codePointAt(string, pos) { + var first = string.charCodeAt(pos), second; + if (first >= 0xD800 && first <= 0xDBFF && pos + 1 < string.length) { + second = string.charCodeAt(pos + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } } -}; -const setextUnderlineMini = { - tokenize: tokenizeSetextUnderlineMini, - partial: true -}; -const nextPrefixedOrBlank = { - tokenize: tokenizeNextPrefixedOrBlank, - partial: true -}; -/** @type {Resolver} */ - -function resolveTable(events, context) { - let index = -1; - /** @type {Token} */ - - let token; - /** @type {boolean|undefined} */ - - let inHead; - /** @type {boolean|undefined} */ - - let inDelimiterRow; - /** @type {boolean|undefined} */ - - let inRow; - /** @type {Token} */ - - let cell; - /** @type {Token} */ - - let content; - /** @type {Token} */ - - let text; - /** @type {number|undefined} */ + return first; +} - let contentStart; - /** @type {number|undefined} */ +// Determines whether block indentation indicator is required. +function needIndentIndicator(string) { + var leadingSpaceRe = /^\n* /; + return leadingSpaceRe.test(string); +} - let contentEnd; - /** @type {number|undefined} */ +var STYLE_PLAIN = 1, + STYLE_SINGLE = 2, + STYLE_LITERAL = 3, + STYLE_FOLDED = 4, + STYLE_DOUBLE = 5; - let cellStart; +// Determines which scalar styles are possible and returns the preferred style. +// lineWidth = -1 => no limit. +// Pre-conditions: str.length > 0. +// Post-conditions: +// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string. +// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1). +// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1). +function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, + testAmbiguousType, quotingType, forceQuotes, inblock) { - while (++index < events.length) { - token = events[index][1]; + var i; + var char = 0; + var prevChar = null; + var hasLineBreak = false; + var hasFoldableLine = false; // only checked if shouldTrackWidth + var shouldTrackWidth = lineWidth !== -1; + var previousLineBreak = -1; // count the first line correctly + var plain = isPlainSafeFirst(codePointAt(string, 0)) + && isPlainSafeLast(codePointAt(string, string.length - 1)); - if (inRow) { - if (token.type === 'temporaryTableCellContent') { - contentStart = contentStart || index; - contentEnd = index; + if (singleLineOnly || forceQuotes) { + // Case: no block styles. + // Check for disallowed characters to rule out plain and single. + for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + if (!isPrintable(char)) { + return STYLE_DOUBLE; } - - if ( - // Combine separate content parts into one. - (token.type === 'tableCellDivider' || token.type === 'tableRow') && - contentEnd - ) { - content = { - type: 'tableContent', - // @ts-expect-error `contentStart` is defined if `contentEnd` is too. - start: events[contentStart][1].start, - end: events[contentEnd][1].end - }; - text = { - type: 'chunkText', - start: content.start, - end: content.end, - // @ts-expect-error It’s fine. - contentType: 'text' - }; - events.splice( - // @ts-expect-error `contentStart` is defined if `contentEnd` is too. - contentStart, // @ts-expect-error `contentStart` is defined if `contentEnd` is too. - contentEnd - contentStart + 1, - ['enter', content, context], - ['enter', text, context], - ['exit', text, context], - ['exit', content, context] - ); // @ts-expect-error `contentStart` is defined if `contentEnd` is too. - - index -= contentEnd - contentStart - 3; - contentStart = undefined; - contentEnd = undefined; + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; + } + } else { + // Case: block styles permitted. + for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + if (char === CHAR_LINE_FEED) { + hasLineBreak = true; + // Check if any line can be folded. + if (shouldTrackWidth) { + hasFoldableLine = hasFoldableLine || + // Foldable line = too long, and not more-indented. + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' '); + previousLineBreak = i; + } + } else if (!isPrintable(char)) { + return STYLE_DOUBLE; } + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; } - - if ( - events[index][0] === 'exit' && - cellStart && - cellStart + 1 < index && - (token.type === 'tableCellDivider' || - (token.type === 'tableRow' && - (cellStart + 3 < index || - events[cellStart][1].type !== 'whitespace'))) - ) { - cell = { - type: inDelimiterRow - ? 'tableDelimiter' - : inHead - ? 'tableHeader' - : 'tableData', - start: events[cellStart][1].start, - end: events[index][1].end - }; - events.splice(index + (token.type === 'tableCellDivider' ? 1 : 0), 0, [ - 'exit', - cell, - context - ]); - events.splice(cellStart, 0, ['enter', cell, context]); - index += 2; - cellStart = index + 1; + // in case the end is missing a \n + hasFoldableLine = hasFoldableLine || (shouldTrackWidth && + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' ')); + } + // Although every style can represent \n without escaping, prefer block styles + // for multiline, since they're more readable and they don't add empty lines. + // Also prefer folding a super-long line. + if (!hasLineBreak && !hasFoldableLine) { + // Strings interpretable as another type have to be quoted; + // e.g. the string 'true' vs. the boolean true. + if (plain && !forceQuotes && !testAmbiguousType(string)) { + return STYLE_PLAIN; } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; + } + // Edge case: block indentation indicator can only have one digit. + if (indentPerLevel > 9 && needIndentIndicator(string)) { + return STYLE_DOUBLE; + } + // At this point we know block styles are valid. + // Prefer literal style unless we want to fold. + if (!forceQuotes) { + return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL; + } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; +} - if (token.type === 'tableRow') { - inRow = events[index][0] === 'enter'; - - if (inRow) { - cellStart = index + 1; +// Note: line breaking/folding is implemented for only the folded style. +// NB. We drop the last trailing newline (if any) of a returned block scalar +// since the dumper adds its own newline. This always works: +// • No ending newline => unaffected; already using strip "-" chomping. +// • Ending newline => removed then restored. +// Importantly, this keeps the "+" chomp indicator from gaining an extra line. +function writeScalar(state, string, level, iskey, inblock) { + state.dump = (function () { + if (string.length === 0) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''"; + } + if (!state.noCompatMode) { + if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? ('"' + string + '"') : ("'" + string + "'"); } } - if (token.type === 'tableDelimiterRow') { - inDelimiterRow = events[index][0] === 'enter'; + var indent = state.indent * Math.max(1, level); // no 0-indent scalars + // As indentation gets deeper, let the width decrease monotonically + // to the lower bound min(state.lineWidth, 40). + // Note that this implies + // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound. + // state.lineWidth > 40 + state.indent: width decreases until the lower bound. + // This behaves better than a constant minimum width which disallows narrower options, + // or an indent threshold which causes the width to suddenly increase. + var lineWidth = state.lineWidth === -1 + ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); - if (inDelimiterRow) { - cellStart = index + 1; - } + // Without knowing if keys are implicit/explicit, assume implicit for safety. + var singleLineOnly = iskey + // No block styles in flow mode. + || (state.flowLevel > -1 && level >= state.flowLevel); + function testAmbiguity(string) { + return testImplicitResolving(state, string); } - if (token.type === 'tableHead') { - inHead = events[index][0] === 'enter'; + switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, + testAmbiguity, state.quotingType, state.forceQuotes && !iskey, inblock)) { + + case STYLE_PLAIN: + return string; + case STYLE_SINGLE: + return "'" + string.replace(/'/g, "''") + "'"; + case STYLE_LITERAL: + return '|' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(string, indent)); + case STYLE_FOLDED: + return '>' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(foldString(string, lineWidth), indent)); + case STYLE_DOUBLE: + return '"' + escapeString(string) + '"'; + default: + throw new exception('impossible error: invalid scalar style'); } - } + }()); +} - return events +// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9. +function blockHeader(string, indentPerLevel) { + var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ''; + + // note the special case: the string '\n' counts as a "trailing" empty line. + var clip = string[string.length - 1] === '\n'; + var keep = clip && (string[string.length - 2] === '\n' || string === '\n'); + var chomp = keep ? '+' : (clip ? '' : '-'); + + return indentIndicator + chomp + '\n'; } -/** @type {Tokenizer} */ -function tokenizeTable(effects, ok, nok) { - const self = this; - /** @type {Align[]} */ +// (See the note for writeScalar.) +function dropEndingNewline(string) { + return string[string.length - 1] === '\n' ? string.slice(0, -1) : string; +} - const align = []; - let tableHeaderCount = 0; - /** @type {boolean|undefined} */ +// Note: a long line without a suitable break point will exceed the width limit. +// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0. +function foldString(string, width) { + // In folded style, $k$ consecutive newlines output as $k+1$ newlines— + // unless they're before or after a more-indented line, or at the very + // beginning or end, in which case $k$ maps to $k$. + // Therefore, parse each chunk as newline(s) followed by a content line. + var lineRe = /(\n+)([^\n]*)/g; - let seenDelimiter; - /** @type {boolean|undefined} */ + // first line (possibly an empty line) + var result = (function () { + var nextLF = string.indexOf('\n'); + nextLF = nextLF !== -1 ? nextLF : string.length; + lineRe.lastIndex = nextLF; + return foldLine(string.slice(0, nextLF), width); + }()); + // If we haven't reached the first content line yet, don't add an extra \n. + var prevMoreIndented = string[0] === '\n' || string[0] === ' '; + var moreIndented; - let hasDash; - return start - /** @type {State} */ + // rest of the lines + var match; + while ((match = lineRe.exec(string))) { + var prefix = match[1], line = match[2]; + moreIndented = (line[0] === ' '); + result += prefix + + (!prevMoreIndented && !moreIndented && line !== '' + ? '\n' : '') + + foldLine(line, width); + prevMoreIndented = moreIndented; + } - function start(code) { - // @ts-expect-error Custom. - effects.enter('table')._align = align; - effects.enter('tableHead'); - effects.enter('tableRow'); // If we start with a pipe, we open a cell marker. + return result; +} - if (code === 124) { - return cellDividerHead(code) - } +// Greedy line breaking. +// Picks the longest line under the limit each time, +// otherwise settles for the shortest line over the limit. +// NB. More-indented lines *cannot* be folded, as that would add an extra \n. +function foldLine(line, width) { + if (line === '' || line[0] === ' ') return line; - tableHeaderCount++; - effects.enter('temporaryTableCellContent'); // Can’t be space or eols at the start of a construct, so we’re in a cell. + // Since a more-indented line adds a \n, breaks can't be followed by a space. + var breakRe = / [^ ]/g; // note: the match index will always be <= length-2. + var match; + // start is an inclusive index. end, curr, and next are exclusive. + var start = 0, end, curr = 0, next = 0; + var result = ''; - return inCellContentHead(code) + // Invariants: 0 <= start <= length-1. + // 0 <= curr <= next <= max(0, length-2). curr - start <= width. + // Inside the loop: + // A match implies length >= 2, so curr and next are <= length-2. + while ((match = breakRe.exec(line))) { + next = match.index; + // maintain invariant: curr - start <= width + if (next - start > width) { + end = (curr > start) ? curr : next; // derive end <= length-2 + result += '\n' + line.slice(start, end); + // skip the space that was output as \n + start = end + 1; // derive start <= length-1 + } + curr = next; } - /** @type {State} */ - function cellDividerHead(code) { - effects.enter('tableCellDivider'); - effects.consume(code); - effects.exit('tableCellDivider'); - seenDelimiter = true; - return cellBreakHead + // By the invariants, start <= length-1, so there is something left over. + // It is either the whole string or a part starting from non-whitespace. + result += '\n'; + // Insert a break if the remainder is too long and there is a break available. + if (line.length - start > width && curr > start) { + result += line.slice(start, curr) + '\n' + line.slice(curr + 1); + } else { + result += line.slice(start); } - /** @type {State} */ - function cellBreakHead(code) { - if (code === null || markdownLineEnding(code)) { - return atRowEndHead(code) - } + return result.slice(1); // drop extra \n joiner +} - if (markdownSpace(code)) { - effects.enter('whitespace'); - effects.consume(code); - return inWhitespaceHead - } +// Escapes a double-quoted string. +function escapeString(string) { + var result = ''; + var char = 0; + var escapeSeq; - if (seenDelimiter) { - seenDelimiter = undefined; - tableHeaderCount++; + for (var i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) { + char = codePointAt(string, i); + escapeSeq = ESCAPE_SEQUENCES[char]; + + if (!escapeSeq && isPrintable(char)) { + result += string[i]; + if (char >= 0x10000) result += string[i + 1]; + } else { + result += escapeSeq || encodeHex(char); } + } - if (code === 124) { - return cellDividerHead(code) - } // Anything else is cell content. + return result; +} - effects.enter('temporaryTableCellContent'); - return inCellContentHead(code) - } - /** @type {State} */ +function writeFlowSequence(state, level, object) { + var _result = '', + _tag = state.tag, + index, + length, + value; - function inWhitespaceHead(code) { - if (markdownSpace(code)) { - effects.consume(code); - return inWhitespaceHead + for (index = 0, length = object.length; index < length; index += 1) { + value = object[index]; + + if (state.replacer) { + value = state.replacer.call(object, String(index), value); } - effects.exit('whitespace'); - return cellBreakHead(code) - } - /** @type {State} */ + // Write only valid elements, put null instead of invalid elements. + if (writeNode(state, level, value, false, false) || + (typeof value === 'undefined' && + writeNode(state, level, null, false, false))) { - function inCellContentHead(code) { - // EOF, whitespace, pipe - if (code === null || code === 124 || markdownLineEndingOrSpace(code)) { - effects.exit('temporaryTableCellContent'); - return cellBreakHead(code) + if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : ''); + _result += state.dump; } - - effects.consume(code); - return code === 92 ? inCellContentEscapeHead : inCellContentHead } - /** @type {State} */ - function inCellContentEscapeHead(code) { - if (code === 92 || code === 124) { - effects.consume(code); - return inCellContentHead - } // Anything else. + state.tag = _tag; + state.dump = '[' + _result + ']'; +} + +function writeBlockSequence(state, level, object, compact) { + var _result = '', + _tag = state.tag, + index, + length, + value; - return inCellContentHead(code) - } - /** @type {State} */ + for (index = 0, length = object.length; index < length; index += 1) { + value = object[index]; - function atRowEndHead(code) { - if (code === null) { - return nok(code) + if (state.replacer) { + value = state.replacer.call(object, String(index), value); } - effects.exit('tableRow'); - effects.exit('tableHead'); - return effects.attempt( - { - tokenize: tokenizeRowEnd, - partial: true - }, - atDelimiterLineStart, - nok - )(code) - } - /** @type {State} */ + // Write only valid elements, put null instead of invalid elements. + if (writeNode(state, level + 1, value, true, true, false, true) || + (typeof value === 'undefined' && + writeNode(state, level + 1, null, true, true, false, true))) { - function atDelimiterLineStart(code) { - // To do: is the lazy setext thing still needed? - return effects.check( - setextUnderlineMini, - nok, // Support an indent before the delimiter row. - factorySpace(effects, rowStartDelimiter, 'linePrefix', 4) - )(code) - } - /** @type {State} */ + if (!compact || _result !== '') { + _result += generateNextLine(state, level); + } - function rowStartDelimiter(code) { - // If there’s another space, or we’re at the EOL/EOF, exit. - if (code === null || markdownLineEndingOrSpace(code)) { - return nok(code) - } + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + _result += '-'; + } else { + _result += '- '; + } - effects.enter('tableDelimiterRow'); - return atDelimiterRowBreak(code) + _result += state.dump; + } } - /** @type {State} */ - function atDelimiterRowBreak(code) { - if (code === null || markdownLineEnding(code)) { - return rowEndDelimiter(code) - } + state.tag = _tag; + state.dump = _result || '[]'; // Empty sequence if no valid values. +} - if (markdownSpace(code)) { - effects.enter('whitespace'); - effects.consume(code); - return inWhitespaceDelimiter - } +function writeFlowMapping(state, level, object) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + pairBuffer; - if (code === 45) { - effects.enter('tableDelimiterFiller'); - effects.consume(code); - hasDash = true; - align.push(null); - return inFillerDelimiter - } + for (index = 0, length = objectKeyList.length; index < length; index += 1) { - if (code === 58) { - effects.enter('tableDelimiterAlignment'); - effects.consume(code); - effects.exit('tableDelimiterAlignment'); - align.push('left'); - return afterLeftAlignment - } // If we start with a pipe, we open a cell marker. + pairBuffer = ''; + if (_result !== '') pairBuffer += ', '; - if (code === 124) { - effects.enter('tableCellDivider'); - effects.consume(code); - effects.exit('tableCellDivider'); - return atDelimiterRowBreak - } + if (state.condenseFlow) pairBuffer += '"'; - return nok(code) - } - /** @type {State} */ + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; - function inWhitespaceDelimiter(code) { - if (markdownSpace(code)) { - effects.consume(code); - return inWhitespaceDelimiter + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); } - effects.exit('whitespace'); - return atDelimiterRowBreak(code) - } - /** @type {State} */ - - function inFillerDelimiter(code) { - if (code === 45) { - effects.consume(code); - return inFillerDelimiter + if (!writeNode(state, level, objectKey, false, false)) { + continue; // Skip this pair because of invalid key; } - effects.exit('tableDelimiterFiller'); + if (state.dump.length > 1024) pairBuffer += '? '; - if (code === 58) { - effects.enter('tableDelimiterAlignment'); - effects.consume(code); - effects.exit('tableDelimiterAlignment'); - align[align.length - 1] = - align[align.length - 1] === 'left' ? 'center' : 'right'; - return afterRightAlignment + pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' '); + + if (!writeNode(state, level, objectValue, false, false)) { + continue; // Skip this pair because of invalid value. } - return atDelimiterRowBreak(code) + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; } - /** @type {State} */ - function afterLeftAlignment(code) { - if (code === 45) { - effects.enter('tableDelimiterFiller'); - effects.consume(code); - hasDash = true; - return inFillerDelimiter - } // Anything else is not ok. + state.tag = _tag; + state.dump = '{' + _result + '}'; +} - return nok(code) +function writeBlockMapping(state, level, object, compact) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + explicitPair, + pairBuffer; + + // Allow sorting keys so that the output file is deterministic + if (state.sortKeys === true) { + // Default sorting + objectKeyList.sort(); + } else if (typeof state.sortKeys === 'function') { + // Custom sort function + objectKeyList.sort(state.sortKeys); + } else if (state.sortKeys) { + // Something is wrong + throw new exception('sortKeys must be a boolean or a function'); } - /** @type {State} */ - function afterRightAlignment(code) { - if (code === null || markdownLineEnding(code)) { - return rowEndDelimiter(code) + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + pairBuffer = ''; + + if (!compact || _result !== '') { + pairBuffer += generateNextLine(state, level); } - if (markdownSpace(code)) { - effects.enter('whitespace'); - effects.consume(code); - return inWhitespaceDelimiter - } // `|` + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; - if (code === 124) { - effects.enter('tableCellDivider'); - effects.consume(code); - effects.exit('tableCellDivider'); - return atDelimiterRowBreak + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); } - return nok(code) - } - /** @type {State} */ + if (!writeNode(state, level + 1, objectKey, true, true, true)) { + continue; // Skip this pair because of invalid key. + } - function rowEndDelimiter(code) { - effects.exit('tableDelimiterRow'); // Exit if there was no dash at all, or if the header cell count is not the - // delimiter cell count. + explicitPair = (state.tag !== null && state.tag !== '?') || + (state.dump && state.dump.length > 1024); - if (!hasDash || tableHeaderCount !== align.length) { - return nok(code) + if (explicitPair) { + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += '?'; + } else { + pairBuffer += '? '; + } } - if (code === null) { - return tableClose(code) + pairBuffer += state.dump; + + if (explicitPair) { + pairBuffer += generateNextLine(state, level); } - return effects.check( - nextPrefixedOrBlank, - tableClose, - effects.attempt( - { - tokenize: tokenizeRowEnd, - partial: true - }, - factorySpace(effects, bodyStart, 'linePrefix', 4), - tableClose - ) - )(code) - } - /** @type {State} */ + if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { + continue; // Skip this pair because of invalid value. + } - function tableClose(code) { - effects.exit('table'); - return ok(code) - } - /** @type {State} */ + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += ':'; + } else { + pairBuffer += ': '; + } - function bodyStart(code) { - effects.enter('tableBody'); - return rowStartBody(code) + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; } - /** @type {State} */ - function rowStartBody(code) { - effects.enter('tableRow'); // If we start with a pipe, we open a cell marker. + state.tag = _tag; + state.dump = _result || '{}'; // Empty mapping if no valid pairs. +} - if (code === 124) { - return cellDividerBody(code) - } +function detectType(state, object, explicit) { + var _result, typeList, index, length, type, style; - effects.enter('temporaryTableCellContent'); // Can’t be space or eols at the start of a construct, so we’re in a cell. + typeList = explicit ? state.explicitTypes : state.implicitTypes; - return inCellContentBody(code) - } - /** @type {State} */ + for (index = 0, length = typeList.length; index < length; index += 1) { + type = typeList[index]; - function cellDividerBody(code) { - effects.enter('tableCellDivider'); - effects.consume(code); - effects.exit('tableCellDivider'); - return cellBreakBody - } - /** @type {State} */ + if ((type.instanceOf || type.predicate) && + (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) && + (!type.predicate || type.predicate(object))) { - function cellBreakBody(code) { - if (code === null || markdownLineEnding(code)) { - return atRowEndBody(code) - } + if (explicit) { + if (type.multi && type.representName) { + state.tag = type.representName(object); + } else { + state.tag = type.tag; + } + } else { + state.tag = '?'; + } - if (markdownSpace(code)) { - effects.enter('whitespace'); - effects.consume(code); - return inWhitespaceBody - } // `|` + if (type.represent) { + style = state.styleMap[type.tag] || type.defaultStyle; - if (code === 124) { - return cellDividerBody(code) - } // Anything else is cell content. + if (_toString.call(type.represent) === '[object Function]') { + _result = type.represent(object, style); + } else if (_hasOwnProperty.call(type.represent, style)) { + _result = type.represent[style](object, style); + } else { + throw new exception('!<' + type.tag + '> tag resolver accepts not "' + style + '" style'); + } - effects.enter('temporaryTableCellContent'); - return inCellContentBody(code) - } - /** @type {State} */ + state.dump = _result; + } - function inWhitespaceBody(code) { - if (markdownSpace(code)) { - effects.consume(code); - return inWhitespaceBody + return true; } + } - effects.exit('whitespace'); - return cellBreakBody(code) + return false; +} + +// Serializes `object` and writes it to global `result`. +// Returns true on success, or false on invalid object. +// +function writeNode(state, level, object, block, compact, iskey, isblockseq) { + state.tag = null; + state.dump = object; + + if (!detectType(state, object, false)) { + detectType(state, object, true); } - /** @type {State} */ - function inCellContentBody(code) { - // EOF, whitespace, pipe - if (code === null || code === 124 || markdownLineEndingOrSpace(code)) { - effects.exit('temporaryTableCellContent'); - return cellBreakBody(code) - } + var type = _toString.call(state.dump); + var inblock = block; + var tagStr; - effects.consume(code); - return code === 92 ? inCellContentEscapeBody : inCellContentBody + if (block) { + block = (state.flowLevel < 0 || state.flowLevel > level); } - /** @type {State} */ - function inCellContentEscapeBody(code) { - if (code === 92 || code === 124) { - effects.consume(code); - return inCellContentBody - } // Anything else. + var objectOrArray = type === '[object Object]' || type === '[object Array]', + duplicateIndex, + duplicate; - return inCellContentBody(code) + if (objectOrArray) { + duplicateIndex = state.duplicates.indexOf(object); + duplicate = duplicateIndex !== -1; } - /** @type {State} */ - function atRowEndBody(code) { - effects.exit('tableRow'); + if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) { + compact = false; + } - if (code === null) { - return tableBodyClose(code) + if (duplicate && state.usedDuplicates[duplicateIndex]) { + state.dump = '*ref_' + duplicateIndex; + } else { + if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { + state.usedDuplicates[duplicateIndex] = true; + } + if (type === '[object Object]') { + if (block && (Object.keys(state.dump).length !== 0)) { + writeBlockMapping(state, level, state.dump, compact); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowMapping(state, level, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object Array]') { + if (block && (state.dump.length !== 0)) { + if (state.noArrayIndent && !isblockseq && level > 0) { + writeBlockSequence(state, level - 1, state.dump, compact); + } else { + writeBlockSequence(state, level, state.dump, compact); + } + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowSequence(state, level, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object String]') { + if (state.tag !== '?') { + writeScalar(state, state.dump, level, iskey, inblock); + } + } else if (type === '[object Undefined]') { + return false; + } else { + if (state.skipInvalid) return false; + throw new exception('unacceptable kind of an object to dump ' + type); } - return effects.check( - nextPrefixedOrBlank, - tableBodyClose, - effects.attempt( - { - tokenize: tokenizeRowEnd, - partial: true - }, - factorySpace(effects, rowStartBody, 'linePrefix', 4), - tableBodyClose - ) - )(code) + if (state.tag !== null && state.tag !== '?') { + // Need to encode all characters except those allowed by the spec: + // + // [35] ns-dec-digit ::= [#x30-#x39] /* 0-9 */ + // [36] ns-hex-digit ::= ns-dec-digit + // | [#x41-#x46] /* A-F */ | [#x61-#x66] /* a-f */ + // [37] ns-ascii-letter ::= [#x41-#x5A] /* A-Z */ | [#x61-#x7A] /* a-z */ + // [38] ns-word-char ::= ns-dec-digit | ns-ascii-letter | “-” + // [39] ns-uri-char ::= “%” ns-hex-digit ns-hex-digit | ns-word-char | “#” + // | “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,” + // | “_” | “.” | “!” | “~” | “*” | “'” | “(” | “)” | “[” | “]” + // + // Also need to encode '!' because it has special meaning (end of tag prefix). + // + tagStr = encodeURI( + state.tag[0] === '!' ? state.tag.slice(1) : state.tag + ).replace(/!/g, '%21'); + + if (state.tag[0] === '!') { + tagStr = '!' + tagStr; + } else if (tagStr.slice(0, 18) === 'tag:yaml.org,2002:') { + tagStr = '!!' + tagStr.slice(18); + } else { + tagStr = '!<' + tagStr + '>'; + } + + state.dump = tagStr + ' ' + state.dump; + } } - /** @type {State} */ - function tableBodyClose(code) { - effects.exit('tableBody'); - return tableClose(code) + return true; +} + +function getDuplicateReferences(object, state) { + var objects = [], + duplicatesIndexes = [], + index, + length; + + inspectNode(object, objects, duplicatesIndexes); + + for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) { + state.duplicates.push(objects[duplicatesIndexes[index]]); } - /** @type {Tokenizer} */ + state.usedDuplicates = new Array(length); +} - function tokenizeRowEnd(effects, ok, nok) { - return start - /** @type {State} */ +function inspectNode(object, objects, duplicatesIndexes) { + var objectKeyList, + index, + length; - function start(code) { - effects.enter('lineEnding'); - effects.consume(code); - effects.exit('lineEnding'); - return lineStart - } - /** @type {State} */ + if (object !== null && typeof object === 'object') { + index = objects.indexOf(object); + if (index !== -1) { + if (duplicatesIndexes.indexOf(index) === -1) { + duplicatesIndexes.push(index); + } + } else { + objects.push(object); - function lineStart(code) { - return self.parser.lazy[self.now().line] ? nok(code) : ok(code) + if (Array.isArray(object)) { + for (index = 0, length = object.length; index < length; index += 1) { + inspectNode(object[index], objects, duplicatesIndexes); + } + } else { + objectKeyList = Object.keys(object); + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes); + } + } } } -} // Based on micromark, but that won’t work as we’re in a table, and that expects -// content. -// +} -/** @type {Tokenizer} */ +function dump$1(input, options) { + options = options || {}; -function tokenizeSetextUnderlineMini(effects, ok, nok) { - return start - /** @type {State} */ + var state = new State(options); - function start(code) { - if (code !== 45) { - return nok(code) - } + if (!state.noRefs) getDuplicateReferences(input, state); - effects.enter('setextUnderline'); - return sequence(code) + var value = input; + + if (state.replacer) { + value = state.replacer.call({ '': value }, '', value); } - /** @type {State} */ - function sequence(code) { - if (code === 45) { - effects.consume(code); - return sequence - } + if (writeNode(state, 0, value, true, true)) return state.dump + '\n'; - return whitespace(code) - } - /** @type {State} */ + return ''; +} - function whitespace(code) { - if (code === null || markdownLineEnding(code)) { - return ok(code) - } +var dump_1 = dump$1; - if (markdownSpace(code)) { - effects.consume(code); - return whitespace - } +var dumper = { + dump: dump_1 +}; - return nok(code) - } +function renamed(from, to) { + return function () { + throw new Error('Function yaml.' + from + ' is removed in js-yaml 4. ' + + 'Use yaml.' + to + ' instead, which is now safe by default.'); + }; } -/** @type {Tokenizer} */ -function tokenizeNextPrefixedOrBlank(effects, ok, nok) { - let size = 0; - return start - /** @type {State} */ - function start(code) { - // This is a check, so we don’t care about tokens, but we open a bogus one - // so we’re valid. - effects.enter('check'); // EOL. +var Type = type; +var Schema = schema; +var FAILSAFE_SCHEMA = failsafe; +var JSON_SCHEMA = json; +var CORE_SCHEMA = core; +var DEFAULT_SCHEMA = _default; +var load = loader.load; +var loadAll = loader.loadAll; +var dump = dumper.dump; +var YAMLException = exception; - effects.consume(code); - return whitespace - } - /** @type {State} */ +// Re-export all types in case user wants to create custom schema +var types = { + binary: binary, + float: float, + map: map, + null: _null, + pairs: pairs, + set: set, + timestamp: timestamp, + bool: bool, + int: int, + merge: merge, + omap: omap, + seq: seq, + str: str +}; - function whitespace(code) { - if (code === -1 || code === 32) { - effects.consume(code); - size++; - return size === 4 ? ok : whitespace - } // EOF or whitespace +// Removed functions from JS-YAML 3.0.x +var safeLoad = renamed('safeLoad', 'load'); +var safeLoadAll = renamed('safeLoadAll', 'loadAll'); +var safeDump = renamed('safeDump', 'dump'); - if (code === null || markdownLineEndingOrSpace(code)) { - return ok(code) - } // Anything else. +var jsYaml = { + Type: Type, + Schema: Schema, + FAILSAFE_SCHEMA: FAILSAFE_SCHEMA, + JSON_SCHEMA: JSON_SCHEMA, + CORE_SCHEMA: CORE_SCHEMA, + DEFAULT_SCHEMA: DEFAULT_SCHEMA, + load: load, + loadAll: loadAll, + dump: dump, + YAMLException: YAMLException, + types: types, + safeLoad: safeLoad, + safeLoadAll: safeLoadAll, + safeDump: safeDump +}; - return nok(code) - } -} +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +const SEMVER_SPEC_VERSION = '2.0.0'; -/** - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - * @typedef {import('micromark-util-types').Token} Token - * @typedef {import('micromark-util-types').CompileContext} CompileContext - */ +const MAX_LENGTH$2 = 256; +const MAX_SAFE_INTEGER$1 = Number.MAX_SAFE_INTEGER || + /* istanbul ignore next */ 9007199254740991; -/** - * An opening or closing tag, followed by a case-insensitive specific tag name, - * followed by HTML whitespace, a greater than, or a slash. - */ -const reFlow = - /<(\/?)(iframe|noembed|noframes|plaintext|script|style|title|textarea|xmp)(?=[\t\n\f\r />])/gi; +// Max safe segment length for coercion. +const MAX_SAFE_COMPONENT_LENGTH = 16; -/** - * As HTML (text) parses tags separately (and v. strictly), we don’t need to be - * global. - */ -const reText = new RegExp('^' + reFlow.source, 'i'); +var constants = { + SEMVER_SPEC_VERSION, + MAX_LENGTH: MAX_LENGTH$2, + MAX_SAFE_INTEGER: MAX_SAFE_INTEGER$1, + MAX_SAFE_COMPONENT_LENGTH +}; -/** @type {HtmlExtension} */ -const gfmTagfilterHtml = { - exit: { - htmlFlowData(token) { - exitHtmlData.call(this, token, reFlow); - }, - htmlTextData(token) { - exitHtmlData.call(this, token, reText); - } - } +var re$2 = {exports: {}}; + +const debug$1 = ( + typeof process === 'object' && + process.env && + process.env.NODE_DEBUG && + /\bsemver\b/i.test(process.env.NODE_DEBUG) +) ? (...args) => console.error('SEMVER', ...args) + : () => {}; + +var debug_1 = debug$1; + +(function (module, exports) { +const { MAX_SAFE_COMPONENT_LENGTH } = constants; +const debug = debug_1; +exports = module.exports = {}; + +// The actual regexps go on exports.re +const re = exports.re = []; +const src = exports.src = []; +const t = exports.t = {}; +let R = 0; + +const createToken = (name, value, isGlobal) => { + const index = R++; + debug(index, value); + t[name] = index; + src[index] = value; + re[index] = new RegExp(value, isGlobal ? 'g' : undefined); }; -/** - * @this {CompileContext} - * @param {Token} token - * @param {RegExp} filter - */ -function exitHtmlData(token, filter) { - let value = this.sliceSerialize(token); +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. - if (this.options.allowDangerousHtml) { - value = value.replace(filter, '<$1$2'); - } +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. - this.raw(this.encode(value)); -} +createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*'); +createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+'); -/** - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - */ +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. -/** @type {HtmlExtension} */ -const gfmTaskListItemHtml = { - enter: { - taskListCheck() { - this.tag(''); - }, +createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*'); + +// ## Main Version +// Three dot-separated numeric identifiers. + +createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + + `(${src[t.NUMERICIDENTIFIER]})\\.` + + `(${src[t.NUMERICIDENTIFIER]})`); + +createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + + `(${src[t.NUMERICIDENTIFIERLOOSE]})`); + +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. + +createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] +}|${src[t.NONNUMERICIDENTIFIER]})`); + +createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] +}|${src[t.NONNUMERICIDENTIFIER]})`); + +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. + +createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] +}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`); - taskListCheckValueChecked() { - this.tag('checked="" '); - } - } -}; +createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] +}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`); -/** - * @typedef {import('micromark-util-types').Extension} Extension - * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord - * @typedef {import('micromark-util-types').Tokenizer} Tokenizer - * @typedef {import('micromark-util-types').Previous} Previous - * @typedef {import('micromark-util-types').State} State - * @typedef {import('micromark-util-types').Event} Event - * @typedef {import('micromark-util-types').Code} Code - */ -const tasklistCheck = { - tokenize: tokenizeTasklistCheck -}; -const gfmTaskListItem = { - text: { - [91]: tasklistCheck - } -}; -/** @type {Tokenizer} */ +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. -function tokenizeTasklistCheck(effects, ok, nok) { - const self = this; - return open - /** @type {State} */ +createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+'); - function open(code) { - if ( - // Exit if there’s stuff before. - self.previous !== null || // Exit if not in the first content that is the first child of a list - // item. - !self._gfmTasklistFirstContentOfListItem - ) { - return nok(code) - } +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. - effects.enter('taskListCheck'); - effects.enter('taskListCheckMarker'); - effects.consume(code); - effects.exit('taskListCheckMarker'); - return inside - } - /** @type {State} */ +createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] +}(?:\\.${src[t.BUILDIDENTIFIER]})*))`); - function inside(code) { - if (markdownSpace(code)) { - effects.enter('taskListCheckValueUnchecked'); - effects.consume(code); - effects.exit('taskListCheckValueUnchecked'); - return close - } +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. - if (code === 88 || code === 120) { - effects.enter('taskListCheckValueChecked'); - effects.consume(code); - effects.exit('taskListCheckValueChecked'); - return close - } +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. - return nok(code) - } - /** @type {State} */ +createToken('FULLPLAIN', `v?${src[t.MAINVERSION] +}${src[t.PRERELEASE]}?${ + src[t.BUILD]}?`); - function close(code) { - if (code === 93) { - effects.enter('taskListCheckMarker'); - effects.consume(code); - effects.exit('taskListCheckMarker'); - effects.exit('taskListCheck'); - return effects.check( - { - tokenize: spaceThenNonSpace - }, - ok, - nok - ) - } +createToken('FULL', `^${src[t.FULLPLAIN]}$`); - return nok(code) - } -} -/** @type {Tokenizer} */ +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] +}${src[t.PRERELEASELOOSE]}?${ + src[t.BUILD]}?`); -function spaceThenNonSpace(effects, ok, nok) { - const self = this; - return factorySpace(effects, after, 'whitespace') - /** @type {State} */ +createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`); - function after(code) { - const tail = self.events[self.events.length - 1]; - return tail && - tail[1].type === 'whitespace' && - code !== null && - !markdownLineEndingOrSpace(code) - ? ok(code) - : nok(code) - } -} +createToken('GTLT', '((?:<|>)?=?)'); -/** - * @typedef {import('micromark-util-types').Extension} Extension - * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension - * @typedef {import('micromark-extension-gfm-strikethrough').Options} Options - */ +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifer, meaning "any version" +// Only the first item is strictly required. +createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`); +createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`); -/** - * Support GFM or markdown on github.com. - * - * @param {Options} [options] - * @returns {Extension} - */ -function gfm(options) { - return combineExtensions([ - gfmAutolinkLiteral, - gfmStrikethrough(options), - gfmTable, - gfmTaskListItem - ]) -} +createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + + `(?:${src[t.PRERELEASE]})?${ + src[t.BUILD]}?` + + `)?)?`); -/** @type {HtmlExtension} */ -combineHtmlExtensions([ - gfmAutolinkLiteralHtml, - gfmStrikethroughHtml, - gfmTableHtml, - gfmTagfilterHtml, - gfmTaskListItemHtml -]); +createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:${src[t.PRERELEASELOOSE]})?${ + src[t.BUILD]}?` + + `)?)?`); -/** - * Get the total count of `character` in `value`. - * - * @param {any} value Content, coerced to string - * @param {string} character Single character to look for - * @return {number} Number of times `character` occurred in `value`. - */ -function ccount(value, character) { - var source = String(value); - var count = 0; - var index; +createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`); +createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`); - if (typeof character !== 'string') { - throw new Error('Expected character') - } +// Coercion. +// Extract anything that could conceivably be a part of a valid semver +createToken('COERCE', `${'(^|[^\\d])' + + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + + `(?:$|[^\\d])`); +createToken('COERCERTL', src[t.COERCE], true); - index = source.indexOf(character); +// Tilde ranges. +// Meaning is "reasonably at or greater than" +createToken('LONETILDE', '(?:~>?)'); - while (index !== -1) { - count++; - index = source.indexOf(character, index + character.length); - } +createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true); +exports.tildeTrimReplace = '$1~'; - return count -} +createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`); +createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`); -/** - * @typedef Options Configuration. - * @property {Test} [ignore] `unist-util-is` test used to assert parents - * - * @typedef {import('mdast').Root} Root - * @typedef {import('mdast').Content} Content - * @typedef {import('mdast').PhrasingContent} PhrasingContent - * @typedef {import('mdast').Text} Text - * @typedef {Content|Root} Node - * @typedef {Extract} Parent - * - * @typedef {import('unist-util-visit-parents').Test} Test - * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult - * - * @typedef RegExpMatchObject - * @property {number} index - * @property {string} input - * - * @typedef {string|RegExp} Find - * @typedef {string|ReplaceFunction} Replace - * - * @typedef {[Find, Replace]} FindAndReplaceTuple - * @typedef {Object.} FindAndReplaceSchema - * @typedef {Array.} FindAndReplaceList - * - * @typedef {[RegExp, ReplaceFunction]} Pair - * @typedef {Array.} Pairs - */ +// Caret ranges. +// Meaning is "at least and backwards compatible with" +createToken('LONECARET', '(?:\\^)'); -const own$1 = {}.hasOwnProperty; +createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true); +exports.caretTrimReplace = '$1^'; + +createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`); +createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`); + +// A simple gt/lt/eq thing, or just "" to indicate "any version" +createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`); +createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`); + +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] +}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true); +exports.comparatorTrimReplace = '$1$2$3'; + +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + + `\\s+-\\s+` + + `(${src[t.XRANGEPLAIN]})` + + `\\s*$`); -/** - * @param tree mdast tree - * @param find Value to find and remove. When `string`, escaped and made into a global `RegExp` - * @param [replace] Value to insert. - * * When `string`, turned into a Text node. - * * When `Function`, called with the results of calling `RegExp.exec` as - * arguments, in which case it can return a single or a list of `Node`, - * a `string` (which is wrapped in a `Text` node), or `false` to not replace - * @param [options] Configuration. - */ -const findAndReplace = - /** - * @type {( - * ((tree: Node, find: Find, replace?: Replace, options?: Options) => Node) & - * ((tree: Node, schema: FindAndReplaceSchema|FindAndReplaceList, options?: Options) => Node) - * )} - **/ - ( - /** - * @param {Node} tree - * @param {Find|FindAndReplaceSchema|FindAndReplaceList} find - * @param {Replace|Options} [replace] - * @param {Options} [options] - */ - function (tree, find, replace, options) { - /** @type {Options|undefined} */ - let settings; - /** @type {FindAndReplaceSchema|FindAndReplaceList} */ - let schema; +createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + + `\\s+-\\s+` + + `(${src[t.XRANGEPLAINLOOSE]})` + + `\\s*$`); - if (typeof find === 'string' || find instanceof RegExp) { - // @ts-expect-error don’t expect options twice. - schema = [[find, replace]]; - settings = options; - } else { - schema = find; - // @ts-expect-error don’t expect replace twice. - settings = replace; - } +// Star ranges basically just allow anything at all. +createToken('STAR', '(<|>)?=?\\s*\\*'); +// >=0.0.0 is like a star +createToken('GTE0', '^\\s*>=\\s*0\.0\.0\\s*$'); +createToken('GTE0PRE', '^\\s*>=\\s*0\.0\.0-0\\s*$'); +}(re$2, re$2.exports)); - if (!settings) { - settings = {}; - } +// parse out just the options we care about so we always get a consistent +// obj with keys in a consistent order. +const opts = ['includePrerelease', 'loose', 'rtl']; +const parseOptions$2 = options => + !options ? {} + : typeof options !== 'object' ? { loose: true } + : opts.filter(k => options[k]).reduce((options, k) => { + options[k] = true; + return options + }, {}); +var parseOptions_1 = parseOptions$2; - const ignored = convert(settings.ignore || []); - const pairs = toPairs(schema); - let pairIndex = -1; +const numeric = /^[0-9]+$/; +const compareIdentifiers$1 = (a, b) => { + const anum = numeric.test(a); + const bnum = numeric.test(b); - while (++pairIndex < pairs.length) { - visitParents(tree, 'text', visitor); - } + if (anum && bnum) { + a = +a; + b = +b; + } - return tree + return a === b ? 0 + : (anum && !bnum) ? -1 + : (bnum && !anum) ? 1 + : a < b ? -1 + : 1 +}; - /** @type {import('unist-util-visit-parents').Visitor} */ - function visitor(node, parents) { - let index = -1; - /** @type {Parent|undefined} */ - let grandparent; +const rcompareIdentifiers = (a, b) => compareIdentifiers$1(b, a); - while (++index < parents.length) { - const parent = /** @type {Parent} */ (parents[index]); +var identifiers = { + compareIdentifiers: compareIdentifiers$1, + rcompareIdentifiers +}; - if ( - ignored( - parent, - // @ts-expect-error mdast vs. unist parent. - grandparent ? grandparent.children.indexOf(parent) : undefined, - grandparent - ) - ) { - return - } +const debug = debug_1; +const { MAX_LENGTH: MAX_LENGTH$1, MAX_SAFE_INTEGER } = constants; +const { re: re$1, t: t$1 } = re$2.exports; - grandparent = parent; - } +const parseOptions$1 = parseOptions_1; +const { compareIdentifiers } = identifiers; +class SemVer$2 { + constructor (version, options) { + options = parseOptions$1(options); - if (grandparent) { - return handler(node, grandparent) - } + if (version instanceof SemVer$2) { + if (version.loose === !!options.loose && + version.includePrerelease === !!options.includePrerelease) { + return version + } else { + version = version.version; } + } else if (typeof version !== 'string') { + throw new TypeError(`Invalid Version: ${version}`) + } - /** - * @param {Text} node - * @param {Parent} parent - * @returns {VisitorResult} - */ - function handler(node, parent) { - const find = pairs[pairIndex][0]; - const replace = pairs[pairIndex][1]; - let start = 0; - // @ts-expect-error: TS is wrong, some of these children can be text. - let index = parent.children.indexOf(node); - /** @type {Array.} */ - let nodes = []; - /** @type {number|undefined} */ - let position; + if (version.length > MAX_LENGTH$1) { + throw new TypeError( + `version is longer than ${MAX_LENGTH$1} characters` + ) + } - find.lastIndex = 0; + debug('SemVer', version, options); + this.options = options; + this.loose = !!options.loose; + // this isn't actually relevant for versions, but keep it so that we + // don't run into trouble passing this.options around. + this.includePrerelease = !!options.includePrerelease; - let match = find.exec(node.value); + const m = version.trim().match(options.loose ? re$1[t$1.LOOSE] : re$1[t$1.FULL]); - while (match) { - position = match.index; - // @ts-expect-error this is perfectly fine, typescript. - let value = replace(...match, { - index: match.index, - input: match.input - }); + if (!m) { + throw new TypeError(`Invalid Version: ${version}`) + } - if (typeof value === 'string') { - value = value.length > 0 ? {type: 'text', value} : undefined; - } + this.raw = version; - if (value !== false) { - if (start !== position) { - nodes.push({ - type: 'text', - value: node.value.slice(start, position) - }); - } + // these are actually numbers + this.major = +m[1]; + this.minor = +m[2]; + this.patch = +m[3]; - if (Array.isArray(value)) { - nodes.push(...value); - } else if (value) { - nodes.push(value); - } + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError('Invalid major version') + } - start = position + match[0].length; - } + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError('Invalid minor version') + } - if (!find.global) { - break - } + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError('Invalid patch version') + } - match = find.exec(node.value); + // numberify any prerelease numeric ids + if (!m[4]) { + this.prerelease = []; + } else { + this.prerelease = m[4].split('.').map((id) => { + if (/^[0-9]+$/.test(id)) { + const num = +id; + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num + } } + return id + }); + } - if (position === undefined) { - nodes = [node]; - index--; - } else { - if (start < node.value.length) { - nodes.push({type: 'text', value: node.value.slice(start)}); - } + this.build = m[5] ? m[5].split('.') : []; + this.format(); + } - parent.children.splice(index, 1, ...nodes); - } + format () { + this.version = `${this.major}.${this.minor}.${this.patch}`; + if (this.prerelease.length) { + this.version += `-${this.prerelease.join('.')}`; + } + return this.version + } - return index + nodes.length + 1 + toString () { + return this.version + } + + compare (other) { + debug('SemVer.compare', this.version, this.options, other); + if (!(other instanceof SemVer$2)) { + if (typeof other === 'string' && other === this.version) { + return 0 } + other = new SemVer$2(other, this.options); } - ); -/** - * @param {FindAndReplaceSchema|FindAndReplaceList} schema - * @returns {Pairs} - */ -function toPairs(schema) { - /** @type {Pairs} */ - const result = []; + if (other.version === this.version) { + return 0 + } - if (typeof schema !== 'object') { - throw new TypeError('Expected array or object as schema') + return this.compareMain(other) || this.comparePre(other) } - if (Array.isArray(schema)) { - let index = -1; - - while (++index < schema.length) { - result.push([ - toExpression(schema[index][0]), - toFunction(schema[index][1]) - ]); + compareMain (other) { + if (!(other instanceof SemVer$2)) { + other = new SemVer$2(other, this.options); } - } else { - /** @type {string} */ - let key; - for (key in schema) { - if (own$1.call(schema, key)) { - result.push([toExpression(key), toFunction(schema[key])]); - } - } + return ( + compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch) + ) } - return result -} - -/** - * @param {Find} find - * @returns {RegExp} - */ -function toExpression(find) { - return typeof find === 'string' ? new RegExp(escapeStringRegexp(find), 'g') : find -} + comparePre (other) { + if (!(other instanceof SemVer$2)) { + other = new SemVer$2(other, this.options); + } -/** - * @param {Replace} replace - * @returns {ReplaceFunction} - */ -function toFunction(replace) { - return typeof replace === 'function' ? replace : () => replace -} + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1 + } else if (!this.prerelease.length && other.prerelease.length) { + return 1 + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0 + } -/** - * @typedef {import('mdast').Link} Link - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-from-markdown').Transform} FromMarkdownTransform - * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle - * @typedef {import('mdast-util-to-markdown/lib/types.js').Options} ToMarkdownExtension - * @typedef {import('mdast-util-find-and-replace').ReplaceFunction} ReplaceFunction - * @typedef {import('mdast-util-find-and-replace').RegExpMatchObject} RegExpMatchObject - * @typedef {import('mdast-util-find-and-replace').PhrasingContent} PhrasingContent - */ + let i = 0; + do { + const a = this.prerelease[i]; + const b = other.prerelease[i]; + debug('prerelease compare', i, a, b); + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) + } -const inConstruct = 'phrasing'; -const notInConstruct = ['autolink', 'link', 'image', 'label']; + compareBuild (other) { + if (!(other instanceof SemVer$2)) { + other = new SemVer$2(other, this.options); + } -/** @type {FromMarkdownExtension} */ -const gfmAutolinkLiteralFromMarkdown = { - transforms: [transformGfmAutolinkLiterals], - enter: { - literalAutolink: enterLiteralAutolink, - literalAutolinkEmail: enterLiteralAutolinkValue, - literalAutolinkHttp: enterLiteralAutolinkValue, - literalAutolinkWww: enterLiteralAutolinkValue - }, - exit: { - literalAutolink: exitLiteralAutolink, - literalAutolinkEmail: exitLiteralAutolinkEmail, - literalAutolinkHttp: exitLiteralAutolinkHttp, - literalAutolinkWww: exitLiteralAutolinkWww + let i = 0; + do { + const a = this.build[i]; + const b = other.build[i]; + debug('prerelease compare', i, a, b); + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) } -}; - -/** @type {ToMarkdownExtension} */ -const gfmAutolinkLiteralToMarkdown = { - unsafe: [ - { - character: '@', - before: '[+\\-.\\w]', - after: '[\\-.\\w]', - inConstruct, - notInConstruct - }, - { - character: '.', - before: '[Ww]', - after: '[\\-.\\w]', - inConstruct, - notInConstruct - }, - {character: ':', before: '[ps]', after: '\\/', inConstruct, notInConstruct} - ] -}; -/** @type {FromMarkdownHandle} */ -function enterLiteralAutolink(token) { - this.enter({type: 'link', title: null, url: '', children: []}, token); -} + // preminor will bump the version up to the next minor release, and immediately + // down to pre-release. premajor and prepatch work the same way. + inc (release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0; + this.patch = 0; + this.minor = 0; + this.major++; + this.inc('pre', identifier); + break + case 'preminor': + this.prerelease.length = 0; + this.patch = 0; + this.minor++; + this.inc('pre', identifier); + break + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0; + this.inc('patch', identifier); + this.inc('pre', identifier); + break + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) { + this.inc('patch', identifier); + } + this.inc('pre', identifier); + break -/** @type {FromMarkdownHandle} */ -function enterLiteralAutolinkValue(token) { - this.config.enter.autolinkProtocol.call(this, token); -} + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if ( + this.minor !== 0 || + this.patch !== 0 || + this.prerelease.length === 0 + ) { + this.major++; + } + this.minor = 0; + this.patch = 0; + this.prerelease = []; + break + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) { + this.minor++; + } + this.patch = 0; + this.prerelease = []; + break + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) { + this.patch++; + } + this.prerelease = []; + break + // This probably shouldn't be used publicly. + // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) { + this.prerelease = [0]; + } else { + let i = this.prerelease.length; + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++; + i = -2; + } + } + if (i === -1) { + // didn't increment anything + this.prerelease.push(0); + } + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) { + this.prerelease = [identifier, 0]; + } + } else { + this.prerelease = [identifier, 0]; + } + } + break -/** @type {FromMarkdownHandle} */ -function exitLiteralAutolinkHttp(token) { - this.config.exit.autolinkProtocol.call(this, token); + default: + throw new Error(`invalid increment argument: ${release}`) + } + this.format(); + this.raw = this.version; + return this + } } -/** @type {FromMarkdownHandle} */ -function exitLiteralAutolinkWww(token) { - this.config.exit.data.call(this, token); - const node = /** @type {Link} */ (this.stack[this.stack.length - 1]); - node.url = 'http://' + this.sliceSerialize(token); -} +var semver = SemVer$2; -/** @type {FromMarkdownHandle} */ -function exitLiteralAutolinkEmail(token) { - this.config.exit.autolinkEmail.call(this, token); -} +const {MAX_LENGTH} = constants; +const { re, t } = re$2.exports; +const SemVer$1 = semver; -/** @type {FromMarkdownHandle} */ -function exitLiteralAutolink(token) { - this.exit(token); -} +const parseOptions = parseOptions_1; +const parse = (version, options) => { + options = parseOptions(options); -/** @type {FromMarkdownTransform} */ -function transformGfmAutolinkLiterals(tree) { - findAndReplace( - tree, - [ - [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl], - [/([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/g, findEmail] - ], - {ignore: ['link', 'linkReference']} - ); -} + if (version instanceof SemVer$1) { + return version + } -/** - * @type {ReplaceFunction} - * @param {string} _ - * @param {string} protocol - * @param {string} domain - * @param {string} path - * @param {RegExpMatchObject} match - */ -// eslint-disable-next-line max-params -function findUrl(_, protocol, domain, path, match) { - let prefix = ''; + if (typeof version !== 'string') { + return null + } - // Not an expected previous character. - if (!previous(match)) { - return false + if (version.length > MAX_LENGTH) { + return null } - // Treat `www` as part of the domain. - if (/^w/i.test(protocol)) { - domain = protocol + domain; - protocol = ''; - prefix = 'http://'; + const r = options.loose ? re[t.LOOSE] : re[t.FULL]; + if (!r.test(version)) { + return null } - if (!isCorrectDomain(domain)) { - return false + try { + return new SemVer$1(version, options) + } catch (er) { + return null } +}; - const parts = splitUrl(domain + path); +var parse_1 = parse; - if (!parts[0]) return false +const SemVer = semver; +const compare$2 = (a, b, loose) => + new SemVer(a, loose).compare(new SemVer(b, loose)); - /** @type {PhrasingContent} */ - const result = { - type: 'link', - title: null, - url: prefix + protocol + parts[0], - children: [{type: 'text', value: protocol + parts[0]}] - }; +var compare_1 = compare$2; - if (parts[1]) { - return [result, {type: 'text', value: parts[1]}] - } +const compare$1 = compare_1; +const lt = (a, b, loose) => compare$1(a, b, loose) < 0; +var lt_1 = lt; - return result +const allowedKeys = [ + "added", + "napiVersion", + "deprecated", + "removed", + "changes", +]; +const changesExpectedKeys = ["version", "pr-url", "description"]; +const VERSION_PLACEHOLDER = "REPLACEME"; +const MAX_SAFE_SEMVER_VERSION = parse_1( + Array.from({ length: 3 }, () => Number.MAX_SAFE_INTEGER).join(".") +); +const validVersionNumberRegex = /^v\d+\.\d+\.\d+$/; +const prUrlRegex = new RegExp("^https://github.com/nodejs/node/pull/\\d+$"); +const privatePRUrl = "https://github.com/nodejs-private/node-private/pull/"; + +let releasedVersions; +let invalidVersionMessage = "version(s) must respect the pattern `vx.x.x` or"; +if (process.env.NODE_RELEASED_VERSIONS) { + console.log("Using release list from env..."); + releasedVersions = process.env.NODE_RELEASED_VERSIONS.split(",").map( + (v) => `v${v}` + ); + invalidVersionMessage = `version not listed in the changelogs, `; } +invalidVersionMessage += `use the placeholder \`${VERSION_PLACEHOLDER}\``; -/** - * @type {ReplaceFunction} - * @param {string} _ - * @param {string} atext - * @param {string} label - * @param {RegExpMatchObject} match - */ -function findEmail(_, atext, label, match) { - if ( - // Not an expected previous character. - !previous(match, true) || - // Label ends in not allowed character. - /[_-\d]$/.test(label) - ) { - return false +const kContainsIllegalKey = Symbol("illegal key"); +const kWrongKeyOrder = Symbol("Wrong key order"); +function unorderedKeys(meta) { + const keys = Object.keys(meta); + let previousKeyIndex = -1; + for (const key of keys) { + const keyIndex = allowedKeys.indexOf(key); + if (keyIndex <= previousKeyIndex) { + return keyIndex === -1 ? kContainsIllegalKey : kWrongKeyOrder; + } + previousKeyIndex = keyIndex; } +} - return { - type: 'link', - title: null, - url: 'mailto:' + atext + '@' + label, - children: [{type: 'text', value: atext + '@' + label}] +function containsInvalidVersionNumber(version) { + if (Array.isArray(version)) { + return version.some(containsInvalidVersionNumber); } -} -/** - * @param {string} domain - * @returns {boolean} - */ -function isCorrectDomain(domain) { - const parts = domain.split('.'); + if (version === undefined || version === VERSION_PLACEHOLDER) return false; if ( - parts.length < 2 || - (parts[parts.length - 1] && - (/_/.test(parts[parts.length - 1]) || - !/[a-zA-Z\d]/.test(parts[parts.length - 1]))) || - (parts[parts.length - 2] && - (/_/.test(parts[parts.length - 2]) || - !/[a-zA-Z\d]/.test(parts[parts.length - 2]))) - ) { - return false + releasedVersions && + // Always ignore 0.0.x and 0.1.x release numbers: + (version[1] !== "0" || (version[3] !== "0" && version[3] !== "1")) + ) + return !releasedVersions.includes(version); + + return !validVersionNumberRegex.test(version); +} +const getValidSemver = (version) => + version === VERSION_PLACEHOLDER ? MAX_SAFE_SEMVER_VERSION : version; +function areVersionsUnordered(versions) { + if (!Array.isArray(versions)) return false; + + for (let index = 1; index < versions.length; index++) { + if ( + lt_1( + getValidSemver(versions[index - 1]), + getValidSemver(versions[index]) + ) + ) { + return true; + } } +} - return true +function invalidChangesKeys(change) { + const keys = Object.keys(change); + const { length } = keys; + if (length !== changesExpectedKeys.length) return true; + for (let index = 0; index < length; index++) { + if (keys[index] !== changesExpectedKeys[index]) return true; + } } +function validateSecurityChange(file, node, change, index) { + if ("commit" in change) { + if (typeof change.commit !== "string" || isNaN(`0x${change.commit}`)) { + file.message( + `changes[${index}]: Ill-formed security change commit ID`, + node + ); + } -/** - * @param {string} url - * @returns {[string, string|undefined]} - */ -function splitUrl(url) { - const trailExec = /[!"&'),.:;<>?\]}]+$/.exec(url); - /** @type {number} */ - let closingParenIndex; - /** @type {number} */ - let openingParens; - /** @type {number} */ - let closingParens; - /** @type {string|undefined} */ - let trail; + if (Object.keys(change)[1] === "commit") { + change = { ...change }; + delete change.commit; + } + } + if (invalidChangesKeys(change)) { + const securityChangeExpectedKeys = [...changesExpectedKeys]; + securityChangeExpectedKeys[0] += "[, commit]"; + file.message( + `changes[${index}]: Invalid keys. Expected keys are: ` + + securityChangeExpectedKeys.join(", "), + node + ); + } +} +function validateChanges(file, node, changes) { + if (!Array.isArray(changes)) + return file.message("`changes` must be a YAML list", node); + + const changesVersions = []; + for (let index = 0; index < changes.length; index++) { + const change = changes[index]; + + const isAncient = + typeof change.version === "string" && change.version.startsWith("v0."); + const isSecurityChange = + !isAncient && + typeof change["pr-url"] === "string" && + change["pr-url"].startsWith(privatePRUrl); + + if (isSecurityChange) { + validateSecurityChange(file, node, change, index); + } else if (!isAncient && invalidChangesKeys(change)) { + file.message( + `changes[${index}]: Invalid keys. Expected keys are: ` + + changesExpectedKeys.join(", "), + node + ); + } - if (trailExec) { - url = url.slice(0, trailExec.index); - trail = trailExec[0]; - closingParenIndex = trail.indexOf(')'); - openingParens = ccount(url, '('); - closingParens = ccount(url, ')'); + if (containsInvalidVersionNumber(change.version)) { + file.message(`changes[${index}]: ${invalidVersionMessage}`, node); + } else if (areVersionsUnordered(change.version)) { + file.message(`changes[${index}]: list of versions is not in order`, node); + } - while (closingParenIndex !== -1 && openingParens > closingParens) { - url += trail.slice(0, closingParenIndex + 1); - trail = trail.slice(closingParenIndex + 1); - closingParenIndex = trail.indexOf(')'); - closingParens++; + if (!isAncient && !isSecurityChange && !prUrlRegex.test(change["pr-url"])) { + file.message( + `changes[${index}]: PR-URL does not match the expected pattern`, + node + ); } - } - return [url, trail] -} + if (typeof change.description !== "string" || !change.description.length) { + file.message( + `changes[${index}]: must contain a non-empty description`, + node + ); + } else if (!change.description.endsWith(".")) { + file.message( + `changes[${index}]: description must end with a period`, + node + ); + } -/** - * @param {RegExpMatchObject} match - * @param {boolean} [email=false] - * @returns {boolean} - */ -function previous(match, email) { - const code = match.input.charCodeAt(match.index - 1); + changesVersions.push( + Array.isArray(change.version) ? change.version[0] : change.version + ); + } - return ( - (match.index === 0 || - unicodeWhitespace(code) || - unicodePunctuation(code)) && - (!email || code !== 47) - ) + if (areVersionsUnordered(changesVersions)) { + file.message("Items in `changes` list are not in order", node); + } } -/** - * @typedef {import('mdast').Delete} Delete - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle - * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension - * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle - */ +function validateMeta(node, file, meta) { + switch (unorderedKeys(meta)) { + case kContainsIllegalKey: + file.message( + "YAML dictionary contains illegal keys. Accepted values are: " + + allowedKeys.join(", "), + node + ); + break; -/** @type {FromMarkdownExtension} */ -const gfmStrikethroughFromMarkdown = { - canContainEols: ['delete'], - enter: {strikethrough: enterStrikethrough}, - exit: {strikethrough: exitStrikethrough} -}; + case kWrongKeyOrder: + file.message( + "YAML dictionary keys should be in this order: " + + allowedKeys.join(", "), + node + ); + break; + } -/** @type {ToMarkdownExtension} */ -const gfmStrikethroughToMarkdown = { - unsafe: [{character: '~', inConstruct: 'phrasing'}], - handlers: {delete: handleDelete} -}; + if (containsInvalidVersionNumber(meta.added)) { + file.message(`Invalid \`added\` value: ${invalidVersionMessage}`, node); + } else if (areVersionsUnordered(meta.added)) { + file.message("Versions in `added` list are not in order", node); + } -handleDelete.peek = peekDelete; + if (containsInvalidVersionNumber(meta.deprecated)) { + file.message( + `Invalid \`deprecated\` value: ${invalidVersionMessage}`, + node + ); + } else if (areVersionsUnordered(meta.deprecated)) { + file.message("Versions in `deprecated` list are not in order", node); + } -/** @type {FromMarkdownHandle} */ -function enterStrikethrough(token) { - this.enter({type: 'delete', children: []}, token); -} + if (containsInvalidVersionNumber(meta.removed)) { + file.message(`Invalid \`removed\` value: ${invalidVersionMessage}`, node); + } else if (areVersionsUnordered(meta.removed)) { + file.message("Versions in `removed` list are not in order", node); + } -/** @type {FromMarkdownHandle} */ -function exitStrikethrough(token) { - this.exit(token); + if ("changes" in meta) { + validateChanges(file, node, meta.changes); + } } -/** - * @type {ToMarkdownHandle} - * @param {Delete} node - */ -function handleDelete(node, _, context) { - const exit = context.enter('emphasis'); - const value = containerPhrasing(node, context, {before: '~', after: '~'}); - exit(); - return '~~' + value + '~~' -} +function validateYAMLComments(tree, file) { + visit$1(tree, "html", function visitor(node) { + if (node.value.startsWith("".length)); -/** @type {ToMarkdownHandle} */ -function peekDelete() { - return '~' + validateMeta(node, file, meta); + } catch (e) { + file.message(e, node); + } + }); } -/** - * @typedef MarkdownTableOptions - * @property {string|null|Array.} [align] - * @property {boolean} [padding=true] - * @property {boolean} [delimiterStart=true] - * @property {boolean} [delimiterStart=true] - * @property {boolean} [delimiterEnd=true] - * @property {boolean} [alignDelimiters=true] - * @property {(value: string) => number} [stringLength] - */ - -/** - * Create a table from a matrix of strings. - * - * @param {Array.>} table - * @param {MarkdownTableOptions} [options] - * @returns {string} - */ -function markdownTable(table, options) { - const settings = options || {}; - const align = (settings.align || []).concat(); - const stringLength = settings.stringLength || defaultStringLength; - /** @type {number[]} Character codes as symbols for alignment per column. */ - const alignments = []; - let rowIndex = -1; - /** @type {string[][]} Cells per row. */ - const cellMatrix = []; - /** @type {number[][]} Sizes of each cell per row. */ - const sizeMatrix = []; - /** @type {number[]} */ - const longestCellByColumn = []; - let mostCellsPerRow = 0; - /** @type {number} */ - let columnIndex; - /** @type {string[]} Cells of current row */ - let row; - /** @type {number[]} Sizes of current row */ - let sizes; - /** @type {number} Sizes of current cell */ - let size; - /** @type {string} Current cell */ - let cell; - /** @type {string[]} Chunks of current line. */ - let line; - /** @type {string} */ - let before; - /** @type {string} */ - let after; - /** @type {number} */ - let code; - - // This is a superfluous loop if we don’t align delimiters, but otherwise we’d - // do superfluous work when aligning, so optimize for aligning. - while (++rowIndex < table.length) { - columnIndex = -1; - row = []; - sizes = []; +const remarkLintNodejsYamlComments = lintRule( + "remark-lint:nodejs-yaml-comments", + validateYAMLComments +); - if (table[rowIndex].length > mostCellsPerRow) { - mostCellsPerRow = table[rowIndex].length; - } +const remarkLintProhibitedStrings = lintRule('remark-lint:prohibited-strings', prohibitedStrings); - while (++columnIndex < table[rowIndex].length) { - cell = serialize(table[rowIndex][columnIndex]); +function testProhibited (val, content) { + let regexpFlags = 'g'; + let no = val.no; - if (settings.alignDelimiters !== false) { - size = stringLength(cell); - sizes[columnIndex] = size; + if (!no) { + no = escapeStringRegexp(val.yes); + regexpFlags += 'i'; + } - if ( - longestCellByColumn[columnIndex] === undefined || - size > longestCellByColumn[columnIndex] - ) { - longestCellByColumn[columnIndex] = size; - } - } + let regexpString = '(? longestCellByColumn[columnIndex]) { - longestCellByColumn[columnIndex] = size; - } + visit$1(ast, 'text', checkText); - sizes[columnIndex] = size; - } + function checkText (node) { + const content = node.value; + const initial = pointStart(node).offset; - row[columnIndex] = cell; + strings.forEach((val) => { + const results = testProhibited(val, content); + if (results.length) { + results.forEach(({ result, index, yes }) => { + const message = val.yes ? `Use "${yes}" instead of "${result}"` : `Do not use "${result}"`; + file.message(message, { + start: myLocation.toPoint(initial + index), + end: myLocation.toPoint(initial + index + [...result].length) + }); + }); + } + }); } +} - // Inject the alignment row. - cellMatrix.splice(1, 0, row); - sizeMatrix.splice(1, 0, sizes); +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module rule-style + * @fileoverview + * Warn when the thematic breaks (horizontal rules) violate a given or + * detected style. + * + * Options: `string`, either a corect thematic breaks such as `***`, or + * `'consistent'`, default: `'consistent'`. + * + * `'consistent'` detects the first used thematic break style and warns when + * subsequent rules use different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * has three settings that define how rules are created: + * + * * [`rule`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrule) + * (default: `*`) — Marker to use + * * [`ruleRepetition`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrulerepetition) + * (default: `3`) — Number of markers to use + * * [`ruleSpaces`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsrulespaces) + * (default: `true`) — Whether to pad markers with spaces + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md", "setting": "* * *"} + * + * * * * + * + * * * * + * + * @example + * {"name": "ok.md", "setting": "_______"} + * + * _______ + * + * _______ + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * *** + * + * * * * + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 3:1-3:6: Rules should use `***` + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} + * + * 1:1: Incorrect preferred rule style: provide a correct markdown rule or `'consistent'` + */ - rowIndex = -1; - /** @type {string[]} */ - const lines = []; +const remarkLintRuleStyle = lintRule( + { + origin: 'remark-lint:rule-style', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-rule-style#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); - while (++rowIndex < cellMatrix.length) { - row = cellMatrix[rowIndex]; - sizes = sizeMatrix[rowIndex]; - columnIndex = -1; - line = []; + if (option !== 'consistent' && /[^-_* ]/.test(option)) { + file.fail( + "Incorrect preferred rule style: provide a correct markdown rule or `'consistent'`" + ); + } - while (++columnIndex < mostCellsPerRow) { - cell = row[columnIndex] || ''; - before = ''; - after = ''; + visit$1(tree, 'thematicBreak', (node) => { + const initial = pointStart(node).offset; + const final = pointEnd(node).offset; - if (settings.alignDelimiters !== false) { - size = longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0); - code = alignments[columnIndex]; + if (typeof initial === 'number' && typeof final === 'number') { + const rule = value.slice(initial, final); - if (code === 114 /* `r` */) { - before = ' '.repeat(size); - } else if (code === 99 /* `c` */) { - if (size % 2) { - before = ' '.repeat(size / 2 + 0.5); - after = ' '.repeat(size / 2 - 0.5); - } else { - before = ' '.repeat(size / 2); - after = before; - } - } else { - after = ' '.repeat(size); + if (option === 'consistent') { + option = rule; + } else if (rule !== option) { + file.message('Rules should use `' + option + '`', node); } } + }); + } +); - if (settings.delimiterStart !== false && !columnIndex) { - line.push('|'); - } +var remarkLintRuleStyle$1 = remarkLintRuleStyle; - if ( - settings.padding !== false && - // Don’t add the opening space if we’re not aligning and the cell is - // empty: there will be a closing space. - !(settings.alignDelimiters === false && cell === '') && - (settings.delimiterStart !== false || columnIndex) - ) { - line.push(' '); - } +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module strong-marker + * @fileoverview + * Warn for violating importance (strong) markers. + * + * Options: `'consistent'`, `'*'`, or `'_'`, default: `'consistent'`. + * + * `'consistent'` detects the first used importance style and warns when + * subsequent importance sequences use different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats importance using an `*` (asterisk) by default. + * Pass + * [`strong: '_'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsstrong) + * to use `_` (underscore) instead. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * **foo** and **bar**. + * + * @example + * {"name": "also-ok.md"} + * + * __foo__ and __bar__. + * + * @example + * {"name": "ok.md", "setting": "*"} + * + * **foo**. + * + * @example + * {"name": "ok.md", "setting": "_"} + * + * __foo__. + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * **foo** and __bar__. + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 1:13-1:20: Strong should use `*` as a marker + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} + * + * 1:1: Incorrect strong marker `💩`: use either `'consistent'`, `'*'`, or `'_'` + */ - if (settings.alignDelimiters !== false) { - line.push(before); - } +const remarkLintStrongMarker = lintRule( + { + origin: 'remark-lint:strong-marker', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-strong-marker#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); - line.push(cell); + if (option !== '*' && option !== '_' && option !== 'consistent') { + file.fail( + 'Incorrect strong marker `' + + option + + "`: use either `'consistent'`, `'*'`, or `'_'`" + ); + } - if (settings.alignDelimiters !== false) { - line.push(after); - } + visit$1(tree, 'strong', (node) => { + const start = pointStart(node).offset; - if (settings.padding !== false) { - line.push(' '); - } + if (typeof start === 'number') { + const marker = /** @type {Marker} */ (value.charAt(start)); - if ( - settings.delimiterEnd !== false || - columnIndex !== mostCellsPerRow - 1 - ) { - line.push('|'); + if (option === 'consistent') { + option = marker; + } else if (marker !== option) { + file.message('Strong should use `' + option + '` as a marker', node); + } } - } - - lines.push( - settings.delimiterEnd === false - ? line.join('').replace(/ +$/, '') - : line.join('') - ); + }); } +); - return lines.join('\n') -} - -/** - * @param {string|null|undefined} [value] - * @returns {string} - */ -function serialize(value) { - return value === null || value === undefined ? '' : String(value) -} - -/** - * @param {string} value - * @returns {number} - */ -function defaultStringLength(value) { - return value.length -} - -/** - * @param {string|null|undefined} value - * @returns {number} - */ -function toAlignment(value) { - const code = typeof value === 'string' ? value.charCodeAt(0) : 0; - - return code === 67 /* `C` */ || code === 99 /* `c` */ - ? 99 /* `c` */ - : code === 76 /* `L` */ || code === 108 /* `l` */ - ? 108 /* `l` */ - : code === 82 /* `R` */ || code === 114 /* `r` */ - ? 114 /* `r` */ - : 0 -} +var remarkLintStrongMarker$1 = remarkLintStrongMarker; /** - * @typedef {import('mdast').AlignType} AlignType - * @typedef {import('mdast').Table} Table - * @typedef {import('mdast').TableRow} TableRow - * @typedef {import('mdast').TableCell} TableCell - * @typedef {import('mdast').InlineCode} InlineCode - * @typedef {import('markdown-table').MarkdownTableOptions} MarkdownTableOptions - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle - * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension - * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle - * @typedef {import('mdast-util-to-markdown').Context} ToMarkdownContext + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module table-cell-padding + * @fileoverview + * Warn when table cells are incorrectly padded. + * + * Options: `'consistent'`, `'padded'`, or `'compact'`, default: `'consistent'`. + * + * `'consistent'` detects the first used cell padding style and warns when + * subsequent cells use different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats tables with padding by default. + * Pass + * [`spacedTable: false`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsspacedtable) + * to not use padding. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md", "setting": "padded", "gfm": true} + * + * | A | B | + * | ----- | ----- | + * | Alpha | Bravo | + * + * @example + * {"name": "not-ok.md", "label": "input", "setting": "padded", "gfm": true} + * + * | A | B | + * | :----|----: | + * | Alpha|Bravo | + * + * | C | D | + * | :----- | ---: | + * |Charlie | Delta| + * + * Too much padding isn’t good either: + * + * | E | F | G | H | + * | :---- | -------- | :----: | -----: | + * | Echo | Foxtrot | Golf | Hotel | + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "padded", "gfm": true} + * + * 3:8: Cell should be padded + * 3:9: Cell should be padded + * 7:2: Cell should be padded + * 7:17: Cell should be padded + * 13:9: Cell should be padded with 1 space, not 2 + * 13:20: Cell should be padded with 1 space, not 2 + * 13:21: Cell should be padded with 1 space, not 2 + * 13:29: Cell should be padded with 1 space, not 2 + * 13:30: Cell should be padded with 1 space, not 2 + * + * @example + * {"name": "ok.md", "setting": "compact", "gfm": true} + * + * |A |B | + * |-----|-----| + * |Alpha|Bravo| + * + * @example + * {"name": "not-ok.md", "label": "input", "setting": "compact", "gfm": true} + * + * | A | B | + * | -----| -----| + * | Alpha| Bravo| + * + * |C | D| + * |:------|-----:| + * |Charlie|Delta | + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "compact", "gfm": true} + * + * 3:2: Cell should be compact + * 3:11: Cell should be compact + * 7:16: Cell should be compact + * + * @example + * {"name": "ok-padded.md", "setting": "consistent", "gfm": true} + * + * | A | B | + * | ----- | ----- | + * | Alpha | Bravo | + * + * | C | D | + * | ------- | ----- | + * | Charlie | Delta | + * + * @example + * {"name": "not-ok-padded.md", "label": "input", "setting": "consistent", "gfm": true} + * + * | A | B | + * | ----- | ----- | + * | Alpha | Bravo | + * + * | C | D | + * | :----- | ----: | + * |Charlie | Delta | + * + * @example + * {"name": "not-ok-padded.md", "label": "output", "setting": "consistent", "gfm": true} + * + * 7:2: Cell should be padded + * + * @example + * {"name": "ok-compact.md", "setting": "consistent", "gfm": true} + * + * |A |B | + * |-----|-----| + * |Alpha|Bravo| + * + * |C |D | + * |-------|-----| + * |Charlie|Delta| + * + * @example + * {"name": "not-ok-compact.md", "label": "input", "setting": "consistent", "gfm": true} + * + * |A |B | + * |-----|-----| + * |Alpha|Bravo| + * + * |C | D| + * |:------|-----:| + * |Charlie|Delta | + * + * @example + * {"name": "not-ok-compact.md", "label": "output", "setting": "consistent", "gfm": true} + * + * 7:16: Cell should be compact + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true, "gfm": true} + * + * 1:1: Incorrect table cell padding style `💩`, expected `'padded'`, `'compact'`, or `'consistent'` + * + * @example + * {"name": "empty.md", "label": "input", "setting": "padded", "gfm": true} * - * @typedef Options - * @property {boolean} [tableCellPadding=true] - * @property {boolean} [tablePipeAlign=true] - * @property {MarkdownTableOptions['stringLength']} [stringLength] + * + * + * | | Alpha | Bravo| + * | ------ | ----- | ---: | + * | Charlie| | Echo| + * + * @example + * {"name": "empty.md", "label": "output", "setting": "padded", "gfm": true} + * + * 3:25: Cell should be padded + * 5:10: Cell should be padded + * 5:25: Cell should be padded + * + * @example + * {"name": "missing-body.md", "setting": "padded", "gfm": true} + * + * + * + * | Alpha | Bravo | Charlie | + * | ----- | ------- | ------- | + * | Delta | + * | Echo | Foxtrot | */ -/** @type {FromMarkdownExtension} */ -const gfmTableFromMarkdown = { - enter: { - table: enterTable, - tableData: enterCell, - tableHeader: enterCell, - tableRow: enterRow +const remarkLintTableCellPadding = lintRule( + { + origin: 'remark-lint:table-cell-padding', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-table-cell-padding#readme' }, - exit: { - codeText: exitCodeText, - table: exitTable, - tableData: exit, - tableHeader: exit, - tableRow: exit - } -}; - -/** @type {FromMarkdownHandle} */ -function enterTable(token) { - /** @type {AlignType[]} */ - // @ts-expect-error: `align` is custom. - const align = token._align; - this.enter({type: 'table', align, children: []}, token); - this.setData('inTable', true); -} + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + if ( + option !== 'padded' && + option !== 'compact' && + option !== 'consistent' + ) { + file.fail( + 'Incorrect table cell padding style `' + + option + + "`, expected `'padded'`, `'compact'`, or `'consistent'`" + ); + } -/** @type {FromMarkdownHandle} */ -function exitTable(token) { - this.exit(token); - this.setData('inTable'); -} + visit$1(tree, 'table', (node) => { + const rows = node.children; + // To do: fix types to always have `align` defined. + /* c8 ignore next */ + const align = node.align || []; + /** @type {number[]} */ + const sizes = []; + /** @type {Entry[]} */ + const entries = []; + let index = -1; -/** @type {FromMarkdownHandle} */ -function enterRow(token) { - this.enter({type: 'tableRow', children: []}, token); -} + // Check align row. + // Because there’s zero to two `:`, and there must be one `-`. + while (++index < align.length) { + const alignment = align[index]; + sizes[index] = alignment === 'center' ? 3 : alignment ? 2 : 1; + } -/** @type {FromMarkdownHandle} */ -function exit(token) { - this.exit(token); -} + index = -1; -/** @type {FromMarkdownHandle} */ -function enterCell(token) { - this.enter({type: 'tableCell', children: []}, token); -} + // Check rows. + while (++index < rows.length) { + const row = rows[index]; + let column = -1; -// Overwrite the default code text data handler to unescape escaped pipes when -// they are in tables. -/** @type {FromMarkdownHandle} */ -function exitCodeText(token) { - let value = this.resume(); + // Check fences (before, between, and after cells). + while (++column < row.children.length) { + const cell = row.children[column]; - if (this.getData('inTable')) { - value = value.replace(/\\([\\|])/g, replace); - } + if (cell.children.length > 0) { + const cellStart = pointStart(cell).offset; + const cellEnd = pointEnd(cell).offset; + const contentStart = pointStart(cell.children[0]).offset; + const contentEnd = pointEnd( + cell.children[cell.children.length - 1] + ).offset; - const node = /** @type {InlineCode} */ (this.stack[this.stack.length - 1]); - node.value = value; - this.exit(token); -} + if ( + typeof cellStart !== 'number' || + typeof cellEnd !== 'number' || + typeof contentStart !== 'number' || + typeof contentEnd !== 'number' + ) { + continue + } -/** - * @param {string} $0 - * @param {string} $1 - * @returns {string} - */ -function replace($0, $1) { - // Pipes work, backslashes don’t (but can’t escape pipes). - return $1 === '|' ? $1 : $0 -} + entries.push({ + node: cell, + start: contentStart - cellStart - (column ? 0 : 1), + end: cellEnd - contentEnd - 1, + column + }); -/** - * @param {Options} [options] - * @returns {ToMarkdownExtension} - */ -function gfmTableToMarkdown(options) { - const settings = options || {}; - const padding = settings.tableCellPadding; - const alignDelimiters = settings.tablePipeAlign; - const stringLength = settings.stringLength; - const around = padding ? ' ' : '|'; + // Detect max space per column. + sizes[column] = Math.max( + // More cells could exist than the align row for generated tables. + /* c8 ignore next */ + sizes[column] || 0, + contentEnd - contentStart + ); + } + } + } - return { - unsafe: [ - {character: '\r', inConstruct: 'tableCell'}, - {character: '\n', inConstruct: 'tableCell'}, - // A pipe, when followed by a tab or space (padding), or a dash or colon - // (unpadded delimiter row), could result in a table. - {atBreak: true, character: '|', after: '[\t :-]'}, - // A pipe in a cell must be encoded. - {character: '|', inConstruct: 'tableCell'}, - // A colon must be followed by a dash, in which case it could start a - // delimiter row. - {atBreak: true, character: ':', after: '-'}, - // A delimiter row can also start with a dash, when followed by more - // dashes, a colon, or a pipe. - // This is a stricter version than the built in check for lists, thematic - // breaks, and setex heading underlines though: - // - {atBreak: true, character: '-', after: '[:|-]'} - ], - handlers: { - table: handleTable, - tableRow: handleTableRow, - tableCell: handleTableCell, - inlineCode: inlineCodeWithTable - } - } + const style = + option === 'consistent' + ? entries[0] && (!entries[0].start || !entries[0].end) + ? 0 + : 1 + : option === 'padded' + ? 1 + : 0; - /** - * @type {ToMarkdownHandle} - * @param {Table} node - */ - function handleTable(node, _, context) { - // @ts-expect-error: fixed in `markdown-table@3.0.1`. - return serializeData(handleTableAsData(node, context), node.align) - } + index = -1; - /** - * This function isn’t really used normally, because we handle rows at the - * table level. - * But, if someone passes in a table row, this ensures we make somewhat sense. - * - * @type {ToMarkdownHandle} - * @param {TableRow} node - */ - function handleTableRow(node, _, context) { - const row = handleTableRowAsData(node, context); - // `markdown-table` will always add an align row - const value = serializeData([row]); - return value.slice(0, value.indexOf('\n')) - } + while (++index < entries.length) { + checkSide('start', entries[index], style, sizes); + checkSide('end', entries[index], style, sizes); + } - /** - * @type {ToMarkdownHandle} - * @param {TableCell} node - */ - function handleTableCell(node, _, context) { - const exit = context.enter('tableCell'); - const subexit = context.enter('phrasing'); - const value = containerPhrasing(node, context, { - before: around, - after: around + return SKIP$1 }); - subexit(); - exit(); - return value - } - /** - * @param {Array.>} matrix - * @param {Array.} [align] - */ - function serializeData(matrix, align) { - return markdownTable(matrix, { - align, - alignDelimiters, - padding, - stringLength - }) - } + /** + * @param {'start'|'end'} side + * @param {Entry} entry + * @param {0|1} style + * @param {number[]} sizes + */ + function checkSide(side, entry, style, sizes) { + const cell = entry.node; + const column = entry.column; + const spacing = entry[side]; - /** - * @param {Table} node - * @param {ToMarkdownContext} context - */ - function handleTableAsData(node, context) { - const children = node.children; - let index = -1; - /** @type {Array.>} */ - const result = []; - const subexit = context.enter('table'); + if (spacing === undefined || spacing === style) { + return + } - while (++index < children.length) { - result[index] = handleTableRowAsData(children[index], context); - } + let reason = 'Cell should be '; - subexit(); + if (style === 0) { + // Ignore every cell except the biggest in the column. + if (size$1(cell) < sizes[column]) { + return + } - return result - } + reason += 'compact'; + } else { + reason += 'padded'; - /** - * @param {TableRow} node - * @param {ToMarkdownContext} context - */ - function handleTableRowAsData(node, context) { - const children = node.children; - let index = -1; - /** @type {Array.} */ - const result = []; - const subexit = context.enter('tableRow'); + if (spacing > style) { + // May be right or center aligned. + if (size$1(cell) < sizes[column]) { + return + } - while (++index < children.length) { - result[index] = handleTableCell(children[index], node, context); - } + reason += ' with 1 space, not ' + spacing; + } + } - subexit(); + /** @type {Point} */ + let point; - return result - } + if (side === 'start') { + point = pointStart(cell); + if (!column) { + point.column++; - /** - * @type {ToMarkdownHandle} - * @param {InlineCode} node - */ - function inlineCodeWithTable(node, parent, context) { - let value = inlineCode(node, parent, context); + if (typeof point.offset === 'number') { + point.offset++; + } + } + } else { + point = pointEnd(cell); + point.column--; - if (context.stack.includes('tableCell')) { - value = value.replace(/\|/g, '\\$&'); - } + if (typeof point.offset === 'number') { + point.offset--; + } + } - return value + file.message(reason, point); + } } +); + +var remarkLintTableCellPadding$1 = remarkLintTableCellPadding; + +/** + * @param {TableCell} node + * @returns {number} + */ +function size$1(node) { + const head = pointStart(node.children[0]).offset; + const tail = pointEnd(node.children[node.children.length - 1]).offset; + // Only called when we’re sure offsets exist. + /* c8 ignore next */ + return typeof head === 'number' && typeof tail === 'number' ? tail - head : 0 } /** - * @typedef {import('mdast').ListItem} ListItem - * @typedef {import('mdast').Paragraph} Paragraph - * @typedef {import('mdast').BlockContent} BlockContent - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle - * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension - * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module table-pipes + * @fileoverview + * Warn when table rows are not fenced with pipes. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * creates fenced rows with initial and final pipes by default. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md", "gfm": true} + * + * | A | B | + * | ----- | ----- | + * | Alpha | Bravo | + * + * @example + * {"name": "not-ok.md", "label": "input", "gfm": true} + * + * A | B + * ----- | ----- + * Alpha | Bravo + * + * @example + * {"name": "not-ok.md", "label": "output", "gfm": true} + * + * 1:1: Missing initial pipe in table fence + * 1:10: Missing final pipe in table fence + * 3:1: Missing initial pipe in table fence + * 3:14: Missing final pipe in table fence */ -/** @type {FromMarkdownExtension} */ -const gfmTaskListItemFromMarkdown = { - exit: { - taskListCheckValueChecked: exitCheck, - taskListCheckValueUnchecked: exitCheck, - paragraph: exitParagraphWithTaskListItem +const reasonStart = 'Missing initial pipe in table fence'; +const reasonEnd = 'Missing final pipe in table fence'; + +const remarkLintTablePipes = lintRule( + { + origin: 'remark-lint:table-pipes', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-table-pipes#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file) => { + const value = String(file); + + visit$1(tree, 'table', (node) => { + let index = -1; + + while (++index < node.children.length) { + const row = node.children[index]; + const start = pointStart(row); + const end = pointEnd(row); + + if ( + typeof start.offset === 'number' && + value.charCodeAt(start.offset) !== 124 + ) { + file.message(reasonStart, start); + } + + if ( + typeof end.offset === 'number' && + value.charCodeAt(end.offset - 1) !== 124 + ) { + file.message(reasonEnd, end); + } + } + }); } -}; +); -/** @type {ToMarkdownExtension} */ -const gfmTaskListItemToMarkdown = { - unsafe: [{atBreak: true, character: '-', after: '[:|-]'}], - handlers: {listItem: listItemWithTaskListItem} -}; +var remarkLintTablePipes$1 = remarkLintTablePipes; + +/** + * @author Titus Wormer + * @copyright 2015 Titus Wormer + * @license MIT + * @module unordered-list-marker-style + * @fileoverview + * Warn when the list item marker style of unordered lists violate a given + * style. + * + * Options: `'consistent'`, `'-'`, `'*'`, or `'+'`, default: `'consistent'`. + * + * `'consistent'` detects the first used list style and warns when subsequent + * lists use different styles. + * + * ## Fix + * + * [`remark-stringify`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify) + * formats unordered lists using `-` (hyphen-minus) by default. + * Pass + * [`bullet: '*'` or `bullet: '+'`](https://github.com/remarkjs/remark/tree/HEAD/packages/remark-stringify#optionsbullet) + * to use `*` (asterisk) or `+` (plus sign) instead. + * + * See [Using remark to fix your Markdown](https://github.com/remarkjs/remark-lint#using-remark-to-fix-your-markdown) + * on how to automatically fix warnings for this rule. + * + * @example + * {"name": "ok.md"} + * + * By default (`'consistent'`), if the file uses only one marker, + * that’s OK. + * + * * Foo + * * Bar + * * Baz + * + * Ordered lists are not affected. + * + * 1. Foo + * 2. Bar + * 3. Baz + * + * @example + * {"name": "ok.md", "setting": "*"} + * + * * Foo + * + * @example + * {"name": "ok.md", "setting": "-"} + * + * - Foo + * + * @example + * {"name": "ok.md", "setting": "+"} + * + * + Foo + * + * @example + * {"name": "not-ok.md", "label": "input"} + * + * * Foo + * - Bar + * + Baz + * + * @example + * {"name": "not-ok.md", "label": "output"} + * + * 2:1-2:6: Marker style should be `*` + * 3:1-3:6: Marker style should be `*` + * + * @example + * {"name": "not-ok.md", "label": "output", "setting": "💩", "positionless": true} + * + * 1:1: Incorrect unordered list item marker style `💩`: use either `'-'`, `'*'`, or `'+'` + */ -/** @type {FromMarkdownHandle} */ -function exitCheck(token) { - // We’re always in a paragraph, in a list item. - this.stack[this.stack.length - 2].checked = - token.type === 'taskListCheckValueChecked'; -} +const markers = new Set(['-', '*', '+']); -/** @type {FromMarkdownHandle} */ -function exitParagraphWithTaskListItem(token) { - const parent = this.stack[this.stack.length - 2]; - /** @type {Paragraph} */ - // @ts-expect-error: must be true. - const node = this.stack[this.stack.length - 1]; - /** @type {BlockContent[]} */ - // @ts-expect-error: check whether `parent` is a `listItem` later. - const siblings = parent.children; - const head = node.children[0]; - let index = -1; - /** @type {Paragraph|undefined} */ - let firstParaghraph; +const remarkLintUnorderedListMarkerStyle = lintRule( + { + origin: 'remark-lint:unordered-list-marker-style', + url: 'https://github.com/remarkjs/remark-lint/tree/main/packages/remark-lint-unordered-list-marker-style#readme' + }, + /** @type {import('unified-lint-rule').Rule} */ + (tree, file, option = 'consistent') => { + const value = String(file); - if ( - parent && - parent.type === 'listItem' && - typeof parent.checked === 'boolean' && - head && - head.type === 'text' - ) { - while (++index < siblings.length) { - const sibling = siblings[index]; - if (sibling.type === 'paragraph') { - firstParaghraph = sibling; - break - } + if (option !== 'consistent' && !markers.has(option)) { + file.fail( + 'Incorrect unordered list item marker style `' + + option + + "`: use either `'-'`, `'*'`, or `'+'`" + ); } - if (firstParaghraph === node) { - // Must start with a space or a tab. - head.value = head.value.slice(1); + visit$1(tree, 'list', (node) => { + if (node.ordered) return - if (head.value.length === 0) { - node.children.shift(); - } else { - // @ts-expect-error: must be true. - head.position.start.column++; - // @ts-expect-error: must be true. - head.position.start.offset++; - // @ts-expect-error: must be true. - node.position.start = Object.assign({}, head.position.start); - } - } - } + let index = -1; - this.exit(token); -} + while (++index < node.children.length) { + const child = node.children[index]; -/** - * @type {ToMarkdownHandle} - * @param {ListItem} node - */ -function listItemWithTaskListItem(node, parent, context) { - const head = node.children[0]; - let value = listItem(node, parent, context); + if (!generated(child)) { + const marker = /** @type {Marker} */ ( + value + .slice( + pointStart(child).offset, + pointStart(child.children[0]).offset + ) + .replace(/\[[x ]?]\s*$/i, '') + .replace(/\s/g, '') + ); - if (typeof node.checked === 'boolean' && head && head.type === 'paragraph') { - value = value.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/, check); + if (option === 'consistent') { + option = marker; + } else if (marker !== option) { + file.message('Marker style should be `' + option + '`', child); + } + } + } + }); } +); - return value +var remarkLintUnorderedListMarkerStyle$1 = remarkLintUnorderedListMarkerStyle; - /** - * @param {string} $0 - * @returns {string} - */ - function check($0) { - return $0 + '[' + (node.checked ? 'x' : ' ') + '] ' - } -} +// @see https://github.com/nodejs/node/blob/HEAD/doc/guides/doc-style-guide.md -/** - * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension - * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension - * - * @typedef {import('mdast-util-gfm-table').Options} Options - */ +// Remove remark-lint-no-auto-link-without-protocol +remarkPresetLintRecommended$1.plugins = + remarkPresetLintRecommended$1.plugins.filter( + (fn) => fn.name !== "remark-lint:no-auto-link-without-protocol" + ); -/** - * @type {Array.} - */ -const gfmFromMarkdown = [ - gfmAutolinkLiteralFromMarkdown, - gfmStrikethroughFromMarkdown, - gfmTableFromMarkdown, - gfmTaskListItemFromMarkdown +// Add in rules alphabetically after Gfm and PresetLintRecommended. +const plugins = [ + remarkGfm, + remarkPresetLintRecommended$1, + [remarkLintBlockquoteIndentation$1, 2], + [remarkLintCheckboxCharacterStyle$1, { checked: "x", unchecked: " " }], + remarkLintCheckboxContentIndent$1, + [remarkLintCodeBlockStyle$1, "fenced"], + remarkLintDefinitionSpacing$1, + [ + remarkLintFencedCodeFlag$1, + { + flags: [ + "bash", + "c", + "cjs", + "coffee", + "console", + "cpp", + "diff", + "http", + "js", + "json", + "markdown", + "mjs", + "powershell", + "r", + "text", + ], + }, + ], + [remarkLintFencedCodeMarker$1, "`"], + [remarkLintFileExtension$1, "md"], + remarkLintFinalDefinition$1, + [remarkLintFirstHeadingLevel$1, 1], + [remarkLintHeadingStyle$1, "atx"], + [remarkLintListItemIndent$1, "space"], + remarkLintMaximumLineLength$1, + remarkLintNoConsecutiveBlankLines$1, + remarkLintNoFileNameArticles$1, + remarkLintNoFileNameConsecutiveDashes$1, + remarkLintNofileNameOuterDashes$1, + remarkLintNoHeadingIndent$1, + remarkLintNoMultipleToplevelHeadings$1, + remarkLintNoShellDollars$1, + remarkLintNoTableIndentation$1, + remarkLintNoTabs$1, + remarkLintNoTrailingSpaces, + remarkLintNodejsLinks, + remarkLintNodejsYamlComments, + [ + remarkLintProhibitedStrings, + [ + { yes: "End-of-Life" }, + { yes: "GitHub" }, + { no: "hostname", yes: "host name" }, + { yes: "JavaScript" }, + { no: "[Ll]ong[ -][Tt]erm [Ss]upport", yes: "Long Term Support" }, + { no: "Node", yes: "Node.js", ignoreNextTo: "-API" }, + { yes: "Node.js" }, + { no: "Node[Jj][Ss]", yes: "Node.js" }, + { no: "Node\\.js's?", yes: "the Node.js" }, + { no: "[Nn]ote that", yes: "" }, + { yes: "RFC" }, + { no: "[Rr][Ff][Cc]\\d+", yes: "RFC " }, + { yes: "Unix" }, + { yes: "V8" }, + ], + ], + remarkLintRuleStyle$1, + [remarkLintStrongMarker$1, "*"], + [remarkLintTableCellPadding$1, "padded"], + remarkLintTablePipes$1, + [remarkLintUnorderedListMarkerStyle$1, "*"], ]; -/** - * @param {Options} [options] - * @returns {ToMarkdownExtension} - */ -function gfmToMarkdown(options) { - return { - extensions: [ - gfmAutolinkLiteralToMarkdown, - gfmStrikethroughToMarkdown, - gfmTableToMarkdown(options), - gfmTaskListItemToMarkdown - ] - } -} - -/** - * @typedef {import('mdast').Root} Root - * @typedef {import('micromark-extension-gfm').Options & import('mdast-util-gfm').Options} Options - */ - -/** - * Plugin to support GitHub Flavored Markdown (GFM). - * - * @type {import('unified').Plugin<[Options?]|void[], Root>} - */ -function remarkGfm(options = {}) { - const data = this.data(); - - add('micromarkExtensions', gfm(options)); - add('fromMarkdownExtensions', gfmFromMarkdown); - add('toMarkdownExtensions', gfmToMarkdown(options)); - - /** - * @param {string} field - * @param {unknown} value - */ - function add(field, value) { - const list = /** @type {unknown[]} */ ( - // Other extensions - /* c8 ignore next 2 */ - data[field] ? data[field] : (data[field] = []) - ); +const settings = { + emphasis: "_", + listItemIndent: 1, + tightDefinitions: true, +}; - list.push(value); - } -} +const remarkPresetLintNode = { plugins, settings }; /** * @typedef {import('vfile').VFileValue} Value @@ -29173,7 +29226,6 @@ if (paths[0] === '--format') { const linter = unified() .use(remarkParse) - .use(remarkGfm) .use(remarkPresetLintNode) .use(remarkStringify); diff --git a/tools/lint-md/lint-md.src.mjs b/tools/lint-md/lint-md.src.mjs index 63d77b496b0b36..258d341f3b5967 100644 --- a/tools/lint-md/lint-md.src.mjs +++ b/tools/lint-md/lint-md.src.mjs @@ -4,7 +4,6 @@ import { unified } from 'unified'; import remarkParse from 'remark-parse'; import remarkStringify from 'remark-stringify'; import presetLintNode from 'remark-preset-lint-node'; -import gfm from 'remark-gfm'; import { read } from 'to-vfile'; import { reporter } from 'vfile-reporter'; @@ -24,7 +23,6 @@ if (paths[0] === '--format') { const linter = unified() .use(remarkParse) - .use(gfm) .use(presetLintNode) .use(remarkStringify); diff --git a/tools/lint-md/package-lock.json b/tools/lint-md/package-lock.json index d8066be8c3cdc4..14ac153c4cb821 100644 --- a/tools/lint-md/package-lock.json +++ b/tools/lint-md/package-lock.json @@ -8,7 +8,6 @@ "name": "lint-md", "version": "1.0.0", "dependencies": { - "remark-gfm": "^2.0.0", "remark-parse": "^10.0.0", "remark-preset-lint-node": "^3.0.1", "remark-stringify": "^10.0.0", @@ -97,15 +96,14 @@ "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, - "node_modules/@types/hast": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", - "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "node_modules/@types/estree-jsx": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-0.0.1.tgz", + "integrity": "sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==", "dependencies": { - "@types/unist": "*" + "@types/estree": "*" } }, "node_modules/@types/mdast": { @@ -122,16 +120,11 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz", - "integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==", + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", + "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==", "dev": true }, - "node_modules/@types/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-ARATsLdrGPUnaBvxLhUlnltcMgn7pQG312S8ccdYlnyijabrX9RN/KN/iGj9Am96CoW8e/K9628BA7Bv4XHdrA==" - }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -222,15 +215,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/character-entities-html4": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.0.0.tgz", - "integrity": "sha512-dwT2xh5ZhUAjyP96k57ilMKoTQyASaw9IAMR9U5c1lCu2RUni6O6jxfpUEdO2RcPT6TJFvr8pqsbami4Jk+2oA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/character-entities-legacy": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-2.0.0.tgz", @@ -254,15 +238,6 @@ "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=" }, - "node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -396,105 +371,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hast-util-from-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", - "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.1.tgz", - "integrity": "sha512-ag0fiZfRWsPiR1udvnSbaazJLGv8qd8E+/e3rW8rUZhbKG4HNJmFL4QkEceN+22BgE+uozXY30z/s+2dL6Z++g==", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", - "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", - "dependencies": { - "@types/hast": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.2.tgz", - "integrity": "sha512-ipLhUTMyyJi9F/LXaNDG9BrRdshP6obCfmUZYbE/+T639IdzqAOkKN4DyrEyID0gbb+rsC3PKf0XlviZwzomhw==", - "dependencies": { - "@types/hast": "^2.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-is-element": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", - "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz", - "integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-void-elements": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.0.tgz", - "integrity": "sha512-4OYzQQsBt0G9bJ/nM9/DDsjm4+fVdzAaPJJcWk5QwA3GIAPxQEeOR0rsI8HbDHQz5Gta8pVvGnnTNSbZVEVvkQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -556,9 +432,9 @@ } }, "node_modules/is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -672,9 +548,12 @@ } }, "node_modules/mdast-comment-marker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-comment-marker/-/mdast-comment-marker-2.0.0.tgz", - "integrity": "sha512-LQ4sf7vUzxz4mQQlzzBDgjaCJO5A0lkIAT9TyeNMfqaP31ooP1Qw9hprf7/V3NCo5FA1nvo5gbnfLVRY79QlDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-comment-marker/-/mdast-comment-marker-2.1.0.tgz", + "integrity": "sha512-/+Cfm8A83PjkqjQDB9iYqHESGuXlriCWAwRGPJjkYmxXrF4r6saxeUlOKNrf+SogTwg9E8uyHRCFHLG6/BAAdA==", + "dependencies": { + "mdast-util-mdx-expression": "^1.1.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -695,9 +574,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.1.tgz", - "integrity": "sha512-KGPH5sDqbov0PWOEtElsLqLYC9tGGaOzznl6ss+rjDJP4bPe1t7T/K3oYwXPKTn+YzPUdorYirbz9pXEkapyYQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.2.tgz", + "integrity": "sha512-gXaxv/5fGdrr9TqSMlQK7FmshK8yR9DvW3+NapMBDm44inORxIZVJa1D3yjrUT9ISu8tB/jjblEkUzyzclquNg==", "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -797,6 +676,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-mdx-expression": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.1.1.tgz", + "integrity": "sha512-RDLRkBFmBKCJl6/fQdxxKL2BqNtoPFoNBmQAlj5ZNKOijIWRKjdhPkeufsUOaexLj+78mhJc+L7d1MYka8/LdQ==", + "dependencies": { + "@types/estree-jsx": "^0.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-markdown": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.2.3.tgz", @@ -1374,11 +1265,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1414,59 +1300,6 @@ "node": ">=4" } }, - "node_modules/property-information": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.0.1.tgz", - "integrity": "sha512-F4WUUAF7fMeF4/JUFHNBWDaKDXi2jbvqBW/y6o5wsf3j19wTZ7S60TmtB5HoBhtgw7NKQRMWuz5vk2PR0CygUg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.0.tgz", - "integrity": "sha512-gZcttmf9R5IYHb8AlI1rlmWqXS1yX0rSB/S5ZGJs8atfYZy2DobvH3Ic/gSzB+HL/+oOHPtBguw1TprfhxXBgQ==", - "dependencies": { - "@types/hast": "^2.0.0", - "rehype-parse": "^8.0.0", - "rehype-stringify": "^9.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-parse": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.3.tgz", - "integrity": "sha512-RGw0CVt+0S6KdvpE8bbP2Db9WXclQcIX7A0ufM3QFqAhTo/ddJMQrrI2j3cijlRPZlGK8R3pRgC8U5HyV76IDw==", - "dependencies": { - "@types/hast": "^2.0.0", - "hast-util-from-parse5": "^7.0.0", - "parse5": "^6.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-stringify": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.2.tgz", - "integrity": "sha512-BuVA6lAEYtOpXO2xuHLohAzz8UNoQAxAqYRqh4QEEtU39Co+P1JBZhw6wXA9hMWp+JLcmrxWH8+UKcNSr443Fw==", - "dependencies": { - "@types/hast": "^2.0.0", - "hast-util-to-html": "^8.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-gfm": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-2.0.0.tgz", @@ -1743,9 +1576,9 @@ } }, "node_modules/remark-lint-maximum-line-length": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-maximum-line-length/-/remark-lint-maximum-line-length-3.1.0.tgz", - "integrity": "sha512-EfXXQsrmdyoMdzlZ0yDjSPeEHv+os444SsTvrwPwrJWxj39fudakweM3xAk2NgDJc7UrcCZzKH+4yNDmL0NcvQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-maximum-line-length/-/remark-lint-maximum-line-length-3.1.1.tgz", + "integrity": "sha512-8F3JvtxFGkqF/iZzTsJxPd5V6Wxcd+CyMdY2j7dL5TsedUTlxuu7tk9Giq17mM/pFlURBZS+714zCnfiuz0AIw==", "dependencies": { "@types/mdast": "^3.0.0", "unified": "^10.0.0", @@ -1796,9 +1629,9 @@ } }, "node_modules/remark-lint-no-consecutive-blank-lines": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-4.1.0.tgz", - "integrity": "sha512-4gn7lPXAw9hw08apENVNvWjdtuNtgtHPA0mjqgjTszXC92IY1YW8zGfxChCLvCJ1bG4/isI6Ctx+ZmmQoZEW1Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-4.1.1.tgz", + "integrity": "sha512-DoHwDW/8wCx6Euiza4gH9QOz4BhxaimLoesbxTfqmYFuri5pEreojwx9WAxmLnMK4iGV2XBZdRhkFKaXQQfgSA==", "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -2161,9 +1994,9 @@ } }, "node_modules/remark-lint-table-cell-padding": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-4.1.0.tgz", - "integrity": "sha512-2FqYGR6IsO5HdmvBe15iwD8XI9xjyMBegKuJWK50bWAsIVdR1b80nfXxKDQ6lRDK6LZ7+iATxzTo0Fl3+TLmgw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-4.1.1.tgz", + "integrity": "sha512-ttsTa4TCgWOoRUwIukyISlSbn+DnZBb4H8MwJYQVXZEV6kWCVhFMBvnjKaWxVpa3Xtlgpo1Yoi4yAjh0p0knSA==", "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -2211,13 +2044,12 @@ } }, "node_modules/remark-message-control": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/remark-message-control/-/remark-message-control-7.0.0.tgz", - "integrity": "sha512-KZySoC97TrMPYfIZ9vJ7wxvQwniy68K6WCY3vmSedDN5YuGfdVOpMj6sjaZQcqbWZV9n7BhrT70E3xaUTtk4hA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/remark-message-control/-/remark-message-control-7.1.0.tgz", + "integrity": "sha512-PNVCm0JV5DikNyrvPYUDN97rL7r+ddy/4GMJpbIiQMS6qJxHJpGdppWOp5YfKHlkrfzddUzTQB/MoS9YCHyNNg==", "dependencies": { "@types/mdast": "^3.0.0", "mdast-comment-marker": "^2.0.0", - "rehype": "^12.0.0", "unified": "^10.0.0", "unified-message-control": "^4.0.0", "vfile": "^5.0.0" @@ -2242,11 +2074,12 @@ } }, "node_modules/remark-preset-lint-node": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.1.0.tgz", - "integrity": "sha512-jWEl5ljKYmIiM2cnZbp7dIYmpFND33RWOCcm5cbp8m2GbOLMOaAW2cVBZe47XLAzpQcOIKaT2BjSsi7Y1h/cig==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.2.0.tgz", + "integrity": "sha512-FWKZOVYiiAd9eRMvcjlJihatuXnzfgD/PEO1Oc8+USZe/3MFH3HcUCsUvoymalJ0YM++ekKTQEYrhsFYtk4PIQ==", "dependencies": { "js-yaml": "^4.0.0", + "remark-gfm": "^2.0.0", "remark-lint-blockquote-indentation": "^3.0.0", "remark-lint-checkbox-character-style": "^4.0.0", "remark-lint-checkbox-content-indent": "^4.0.0", @@ -2279,7 +2112,7 @@ "remark-preset-lint-recommended": "^6.0.0", "semver": "^7.3.2", "unified-lint-rule": "^2.0.0", - "unist-util-visit": "^4.0.0" + "unist-util-visit": "^4.1.0" }, "engines": { "node": ">=12.0.0" @@ -2342,9 +2175,9 @@ } }, "node_modules/rollup": { - "version": "2.57.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.57.0.tgz", - "integrity": "sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==", + "version": "2.58.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.58.0.tgz", + "integrity": "sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -2381,15 +2214,6 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, - "node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/string-width": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", @@ -2406,19 +2230,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stringify-entities": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.1.tgz", - "integrity": "sha512-gmMQxKXPWIO3NXNSPyWNhlYcBNGpPA/487D+9dLPnU4xBnIrnHdr8cv5rGJOS/1BRxEXRb7uKwg7BA36IWV7xg==", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", @@ -2573,9 +2384,9 @@ } }, "node_modules/unist-util-visit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.0.0.tgz", - "integrity": "sha512-3HWTvrtU10/E7qgPznBfiOyG0TXj9W8c1GSfaI8L9GkaG1pLePiQPZ7E35a0R3ToQ/zcy4Im6aZ9WBgOTnv1MQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz", + "integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==", "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", @@ -2694,15 +2505,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/web-namespaces": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.0.tgz", - "integrity": "sha512-dE7ELZRVWh0ceQsRgkjLgsAvwTuv3kcjSY/hLjqL0llleUlQBDjE9JkB9FCBY5F2mnFEwiyJoowl8+NVGHe8dw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/wrapped": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wrapped/-/wrapped-1.0.1.tgz", @@ -2793,15 +2595,14 @@ "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, - "@types/hast": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", - "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "@types/estree-jsx": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-0.0.1.tgz", + "integrity": "sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==", "requires": { - "@types/unist": "*" + "@types/estree": "*" } }, "@types/mdast": { @@ -2818,16 +2619,11 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "@types/node": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz", - "integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==", + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", + "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==", "dev": true }, - "@types/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-ARATsLdrGPUnaBvxLhUlnltcMgn7pQG312S8ccdYlnyijabrX9RN/KN/iGj9Am96CoW8e/K9628BA7Bv4XHdrA==" - }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -2894,11 +2690,6 @@ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.0.tgz", "integrity": "sha512-oHqMj3eAuJ77/P5PaIRcqk+C3hdfNwyCD2DAUcD5gyXkegAuF2USC40CEqPscDk4I8FRGMTojGJQkXDsN5QlJA==" }, - "character-entities-html4": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.0.0.tgz", - "integrity": "sha512-dwT2xh5ZhUAjyP96k57ilMKoTQyASaw9IAMR9U5c1lCu2RUni6O6jxfpUEdO2RcPT6TJFvr8pqsbami4Jk+2oA==" - }, "character-entities-legacy": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-2.0.0.tgz", @@ -2914,11 +2705,6 @@ "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=" }, - "comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==" - }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -3013,77 +2799,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==" }, - "hast-util-from-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", - "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", - "requires": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" - } - }, - "hast-util-is-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.1.tgz", - "integrity": "sha512-ag0fiZfRWsPiR1udvnSbaazJLGv8qd8E+/e3rW8rUZhbKG4HNJmFL4QkEceN+22BgE+uozXY30z/s+2dL6Z++g==", - "requires": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0" - } - }, - "hast-util-parse-selector": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", - "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", - "requires": { - "@types/hast": "^2.0.0" - } - }, - "hast-util-to-html": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.2.tgz", - "integrity": "sha512-ipLhUTMyyJi9F/LXaNDG9BrRdshP6obCfmUZYbE/+T639IdzqAOkKN4DyrEyID0gbb+rsC3PKf0XlviZwzomhw==", - "requires": { - "@types/hast": "^2.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-is-element": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "unist-util-is": "^5.0.0" - } - }, - "hast-util-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", - "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==" - }, - "hastscript": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz", - "integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==", - "requires": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - } - }, - "html-void-elements": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.0.tgz", - "integrity": "sha512-4OYzQQsBt0G9bJ/nM9/DDsjm4+fVdzAaPJJcWk5QwA3GIAPxQEeOR0rsI8HbDHQz5Gta8pVvGnnTNSbZVEVvkQ==" - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3120,9 +2835,9 @@ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -3199,9 +2914,12 @@ "integrity": "sha512-CBbaYXKSGnE1uLRpKA1SWgIRb2PQrpkllNWpZtZe6VojOJ4ysqiq7/2glYcmKsOYN09QgH/HEBX5hIshAeiK6A==" }, "mdast-comment-marker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-comment-marker/-/mdast-comment-marker-2.0.0.tgz", - "integrity": "sha512-LQ4sf7vUzxz4mQQlzzBDgjaCJO5A0lkIAT9TyeNMfqaP31ooP1Qw9hprf7/V3NCo5FA1nvo5gbnfLVRY79QlDQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-comment-marker/-/mdast-comment-marker-2.1.0.tgz", + "integrity": "sha512-/+Cfm8A83PjkqjQDB9iYqHESGuXlriCWAwRGPJjkYmxXrF4r6saxeUlOKNrf+SogTwg9E8uyHRCFHLG6/BAAdA==", + "requires": { + "mdast-util-mdx-expression": "^1.1.0" + } }, "mdast-util-find-and-replace": { "version": "2.1.0", @@ -3214,9 +2932,9 @@ } }, "mdast-util-from-markdown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.1.tgz", - "integrity": "sha512-KGPH5sDqbov0PWOEtElsLqLYC9tGGaOzznl6ss+rjDJP4bPe1t7T/K3oYwXPKTn+YzPUdorYirbz9pXEkapyYQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.2.tgz", + "integrity": "sha512-gXaxv/5fGdrr9TqSMlQK7FmshK8yR9DvW3+NapMBDm44inORxIZVJa1D3yjrUT9ISu8tB/jjblEkUzyzclquNg==", "requires": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -3288,6 +3006,14 @@ "@types/mdast": "^3.0.0" } }, + "mdast-util-mdx-expression": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-1.1.1.tgz", + "integrity": "sha512-RDLRkBFmBKCJl6/fQdxxKL2BqNtoPFoNBmQAlj5ZNKOijIWRKjdhPkeufsUOaexLj+78mhJc+L7d1MYka8/LdQ==", + "requires": { + "@types/estree-jsx": "^0.0.1" + } + }, "mdast-util-to-markdown": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.2.3.tgz", @@ -3616,11 +3342,6 @@ "is-hexadecimal": "^2.0.0" } }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3644,43 +3365,6 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" }, - "property-information": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.0.1.tgz", - "integrity": "sha512-F4WUUAF7fMeF4/JUFHNBWDaKDXi2jbvqBW/y6o5wsf3j19wTZ7S60TmtB5HoBhtgw7NKQRMWuz5vk2PR0CygUg==" - }, - "rehype": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.0.tgz", - "integrity": "sha512-gZcttmf9R5IYHb8AlI1rlmWqXS1yX0rSB/S5ZGJs8atfYZy2DobvH3Ic/gSzB+HL/+oOHPtBguw1TprfhxXBgQ==", - "requires": { - "@types/hast": "^2.0.0", - "rehype-parse": "^8.0.0", - "rehype-stringify": "^9.0.0", - "unified": "^10.0.0" - } - }, - "rehype-parse": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.3.tgz", - "integrity": "sha512-RGw0CVt+0S6KdvpE8bbP2Db9WXclQcIX7A0ufM3QFqAhTo/ddJMQrrI2j3cijlRPZlGK8R3pRgC8U5HyV76IDw==", - "requires": { - "@types/hast": "^2.0.0", - "hast-util-from-parse5": "^7.0.0", - "parse5": "^6.0.0", - "unified": "^10.0.0" - } - }, - "rehype-stringify": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.2.tgz", - "integrity": "sha512-BuVA6lAEYtOpXO2xuHLohAzz8UNoQAxAqYRqh4QEEtU39Co+P1JBZhw6wXA9hMWp+JLcmrxWH8+UKcNSr443Fw==", - "requires": { - "@types/hast": "^2.0.0", - "hast-util-to-html": "^8.0.0", - "unified": "^10.0.0" - } - }, "remark-gfm": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-2.0.0.tgz", @@ -3889,9 +3573,9 @@ } }, "remark-lint-maximum-line-length": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-maximum-line-length/-/remark-lint-maximum-line-length-3.1.0.tgz", - "integrity": "sha512-EfXXQsrmdyoMdzlZ0yDjSPeEHv+os444SsTvrwPwrJWxj39fudakweM3xAk2NgDJc7UrcCZzKH+4yNDmL0NcvQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-maximum-line-length/-/remark-lint-maximum-line-length-3.1.1.tgz", + "integrity": "sha512-8F3JvtxFGkqF/iZzTsJxPd5V6Wxcd+CyMdY2j7dL5TsedUTlxuu7tk9Giq17mM/pFlURBZS+714zCnfiuz0AIw==", "requires": { "@types/mdast": "^3.0.0", "unified": "^10.0.0", @@ -3930,9 +3614,9 @@ } }, "remark-lint-no-consecutive-blank-lines": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-4.1.0.tgz", - "integrity": "sha512-4gn7lPXAw9hw08apENVNvWjdtuNtgtHPA0mjqgjTszXC92IY1YW8zGfxChCLvCJ1bG4/isI6Ctx+ZmmQoZEW1Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-no-consecutive-blank-lines/-/remark-lint-no-consecutive-blank-lines-4.1.1.tgz", + "integrity": "sha512-DoHwDW/8wCx6Euiza4gH9QOz4BhxaimLoesbxTfqmYFuri5pEreojwx9WAxmLnMK4iGV2XBZdRhkFKaXQQfgSA==", "requires": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -4213,9 +3897,9 @@ } }, "remark-lint-table-cell-padding": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-4.1.0.tgz", - "integrity": "sha512-2FqYGR6IsO5HdmvBe15iwD8XI9xjyMBegKuJWK50bWAsIVdR1b80nfXxKDQ6lRDK6LZ7+iATxzTo0Fl3+TLmgw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/remark-lint-table-cell-padding/-/remark-lint-table-cell-padding-4.1.1.tgz", + "integrity": "sha512-ttsTa4TCgWOoRUwIukyISlSbn+DnZBb4H8MwJYQVXZEV6kWCVhFMBvnjKaWxVpa3Xtlgpo1Yoi4yAjh0p0knSA==", "requires": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", @@ -4251,13 +3935,12 @@ } }, "remark-message-control": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/remark-message-control/-/remark-message-control-7.0.0.tgz", - "integrity": "sha512-KZySoC97TrMPYfIZ9vJ7wxvQwniy68K6WCY3vmSedDN5YuGfdVOpMj6sjaZQcqbWZV9n7BhrT70E3xaUTtk4hA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/remark-message-control/-/remark-message-control-7.1.0.tgz", + "integrity": "sha512-PNVCm0JV5DikNyrvPYUDN97rL7r+ddy/4GMJpbIiQMS6qJxHJpGdppWOp5YfKHlkrfzddUzTQB/MoS9YCHyNNg==", "requires": { "@types/mdast": "^3.0.0", "mdast-comment-marker": "^2.0.0", - "rehype": "^12.0.0", "unified": "^10.0.0", "unified-message-control": "^4.0.0", "vfile": "^5.0.0" @@ -4274,11 +3957,12 @@ } }, "remark-preset-lint-node": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.1.0.tgz", - "integrity": "sha512-jWEl5ljKYmIiM2cnZbp7dIYmpFND33RWOCcm5cbp8m2GbOLMOaAW2cVBZe47XLAzpQcOIKaT2BjSsi7Y1h/cig==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.2.0.tgz", + "integrity": "sha512-FWKZOVYiiAd9eRMvcjlJihatuXnzfgD/PEO1Oc8+USZe/3MFH3HcUCsUvoymalJ0YM++ekKTQEYrhsFYtk4PIQ==", "requires": { "js-yaml": "^4.0.0", + "remark-gfm": "^2.0.0", "remark-lint-blockquote-indentation": "^3.0.0", "remark-lint-checkbox-character-style": "^4.0.0", "remark-lint-checkbox-content-indent": "^4.0.0", @@ -4311,7 +3995,7 @@ "remark-preset-lint-recommended": "^6.0.0", "semver": "^7.3.2", "unified-lint-rule": "^2.0.0", - "unist-util-visit": "^4.0.0" + "unist-util-visit": "^4.1.0" } }, "remark-preset-lint-recommended": { @@ -4360,9 +4044,9 @@ } }, "rollup": { - "version": "2.57.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.57.0.tgz", - "integrity": "sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==", + "version": "2.58.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.58.0.tgz", + "integrity": "sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -4387,11 +4071,6 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, - "space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==" - }, "string-width": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", @@ -4402,15 +4081,6 @@ "strip-ansi": "^7.0.1" } }, - "stringify-entities": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.1.tgz", - "integrity": "sha512-gmMQxKXPWIO3NXNSPyWNhlYcBNGpPA/487D+9dLPnU4xBnIrnHdr8cv5rGJOS/1BRxEXRb7uKwg7BA36IWV7xg==", - "requires": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^2.0.0" - } - }, "strip-ansi": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", @@ -4515,9 +4185,9 @@ } }, "unist-util-visit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.0.0.tgz", - "integrity": "sha512-3HWTvrtU10/E7qgPznBfiOyG0TXj9W8c1GSfaI8L9GkaG1pLePiQPZ7E35a0R3ToQ/zcy4Im6aZ9WBgOTnv1MQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz", + "integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", @@ -4602,11 +4272,6 @@ "vfile-message": "^3.0.0" } }, - "web-namespaces": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.0.tgz", - "integrity": "sha512-dE7ELZRVWh0ceQsRgkjLgsAvwTuv3kcjSY/hLjqL0llleUlQBDjE9JkB9FCBY5F2mnFEwiyJoowl8+NVGHe8dw==" - }, "wrapped": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wrapped/-/wrapped-1.0.1.tgz", diff --git a/tools/lint-md/package.json b/tools/lint-md/package.json index 31b740744d186e..60c68f41eea7c0 100644 --- a/tools/lint-md/package.json +++ b/tools/lint-md/package.json @@ -6,7 +6,6 @@ "build": "rollup -f es -p '@rollup/plugin-node-resolve={exportConditions: [\"node\"]}' -p @rollup/plugin-commonjs lint-md.src.mjs --file lint-md.mjs" }, "dependencies": { - "remark-gfm": "^2.0.0", "remark-parse": "^10.0.0", "remark-preset-lint-node": "^3.0.1", "remark-stringify": "^10.0.0",