From 76249170691b8bf08e5cd81a13f123f3586525d0 Mon Sep 17 00:00:00 2001
From: Rich Trott <rtrott@gmail.com>
Date: Wed, 13 Oct 2021 11:51:19 -0700
Subject: [PATCH] tools: update tools/lint-md dependencies to support GFM
 footnotes

PR-URL: https://github.com/nodejs/node/pull/40445
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
---
 tools/lint-md/lint-md.mjs       | 1020 +++++++++++++++++--------------
 tools/lint-md/package-lock.json |  154 +++--
 tools/lint-md/package.json      |    4 +-
 3 files changed, 684 insertions(+), 494 deletions(-)

diff --git a/tools/lint-md/lint-md.mjs b/tools/lint-md/lint-md.mjs
index bd893097132255..9d3903a1d4b478 100644
--- a/tools/lint-md/lint-md.mjs
+++ b/tools/lint-md/lint-md.mjs
@@ -1738,50 +1738,6 @@ function constructs(existing, list) {
   splice(existing, 0, 0, before);
 }
 
-/**
- * Combine several HTML extensions into one.
- *
- * @param {HtmlExtension[]} htmlExtensions List of HTML extensions.
- * @returns {HtmlExtension} A single combined extension.
- */
-function combineHtmlExtensions(htmlExtensions) {
-  /** @type {HtmlExtension} */
-  const handlers = {};
-  let index = -1;
-
-  while (++index < htmlExtensions.length) {
-    htmlExtension(handlers, htmlExtensions[index]);
-  }
-
-  return handlers
-}
-
-/**
- * Merge `extension` into `all`.
- *
- * @param {HtmlExtension} all Extension to merge into.
- * @param {HtmlExtension} extension Extension to merge.
- * @returns {void}
- */
-function htmlExtension(all, extension) {
-  /** @type {string} */
-  let hook;
-
-  for (hook in extension) {
-    const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined;
-    const left = maybe || (all[hook] = {});
-    const right = extension[hook];
-    /** @type {string} */
-    let type;
-
-    if (right) {
-      for (type in right) {
-        left[type] = right[type];
-      }
-    }
-  }
-}
-
 // This module is generated by `script/`.
 //
 // CommonMark handles attention (emphasis, strong) markers based on what comes
@@ -8640,7 +8596,7 @@ const listItemPrefixWhitespaceConstruct = {
 /** @type {Construct} */
 
 const indentConstruct = {
-  tokenize: tokenizeIndent,
+  tokenize: tokenizeIndent$1,
   partial: true
 };
 /**
@@ -8820,7 +8776,7 @@ function tokenizeListContinuation(effects, ok, nok) {
  * @this {TokenizeContextWithState}
  */
 
-function tokenizeIndent(effects, ok, nok) {
+function tokenizeIndent$1(effects, ok, nok) {
   const self = this;
   return factorySpace(
     effects,
@@ -13827,7 +13783,8 @@ function joinDefinition(left, right) {
 
 /**
  * @typedef {import('mdast').Root|import('mdast').Content} Node
- * @typedef {import('mdast-util-to-markdown').Options} Options
+ * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownOptions
+ * @typedef {Omit<ToMarkdownOptions, 'extensions'>} Options
  */
 
 /** @type {import('unified').Plugin<[Options]|void[], Node, string>} */
@@ -13843,7 +13800,10 @@ function remarkStringify(options) {
         // Note: this option is not in the readme.
         // The goal is for it to be set by plugins on `data` instead of being
         // passed by users.
-        extensions: this.data('toMarkdownExtensions') || []
+        extensions:
+          /** @type {ToMarkdownOptions['extensions']} */ (
+            this.data('toMarkdownExtensions')
+          ) || []
       })
     )
   };
@@ -14479,201 +14439,419 @@ function previousUnbalanced(events) {
   return result
 }
 
-const characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'};
-
 /**
- * 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}
+ * @typedef {import('micromark-util-types').Extension} Extension
+ * @typedef {import('micromark-util-types').Resolver} Resolver
+ * @typedef {import('micromark-util-types').Token} Token
+ * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
+ * @typedef {import('micromark-util-types').Exiter} Exiter
+ * @typedef {import('micromark-util-types').State} State
+ * @typedef {import('micromark-util-types').Event} Event
+ */
+const indent = {
+  tokenize: tokenizeIndent,
+  partial: true
+};
+/**
+ * @returns {Extension}
  */
-function encode(value) {
-  return value.replace(/["&<>]/g, replace)
 
-  /**
-   * @param {string} value
-   * @returns {string}
-   */
-  function replace(value) {
-    // @ts-expect-error Hush, it’s fine.
-    return '&' + characterReferences[value] + ';'
+function gfmFootnote() {
+  /** @type {Extension} */
+  return {
+    document: {
+      [91]: {
+        tokenize: tokenizeDefinitionStart,
+        continuation: {
+          tokenize: tokenizeDefinitionContinuation
+        },
+        exit: gfmFootnoteDefinitionEnd
+      }
+    },
+    text: {
+      [91]: {
+        tokenize: tokenizeGfmFootnoteCall
+      },
+      [93]: {
+        add: 'after',
+        tokenize: tokenizePotentialGfmFootnoteCall,
+        resolveTo: resolveToPotentialGfmFootnoteCall
+      }
+    }
   }
 }
+/** @type {Tokenizer} */
 
-/**
- * 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 || ''));
+function tokenizePotentialGfmFootnoteCall(effects, ok, nok) {
+  const self = this;
+  let index = self.events.length;
+  /** @type {string[]} */
+  // @ts-expect-error It’s fine!
 
-  if (!protocol) {
-    return value
+  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
+  /** @type {Token} */
+
+  let labelStart; // Find an opening.
+
+  while (index--) {
+    const token = self.events[index][1];
+
+    if (token.type === 'labelImage') {
+      labelStart = token;
+      break
+    } // Exit if we’ve walked far enough.
+
+    if (
+      token.type === 'gfmFootnoteCall' ||
+      token.type === 'labelLink' ||
+      token.type === 'label' ||
+      token.type === 'image' ||
+      token.type === 'link'
+    ) {
+      break
+    }
   }
 
-  const colon = value.indexOf(':');
-  const questionMark = value.indexOf('?');
-  const numberSign = value.indexOf('#');
-  const slash = value.indexOf('/');
+  return start
+  /** @type {State} */
 
-  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
+  function start(code) {
+    if (!labelStart || !labelStart._balanced) {
+      return nok(code)
+    }
+
+    const id = normalizeIdentifier(
+      self.sliceSerialize({
+        start: labelStart.end,
+        end: self.now()
+      })
+    );
+
+    if (id.charCodeAt(0) !== 94 || !defined.includes(id.slice(1))) {
+      return nok(code)
+    }
+
+    effects.enter('gfmFootnoteCallLabelMarker');
+    effects.consume(code);
+    effects.exit('gfmFootnoteCallLabelMarker');
+    return ok(code)
   }
+}
+/** @type {Resolver} */
 
-  return ''
+function resolveToPotentialGfmFootnoteCall(events, context) {
+  let index = events.length;
+
+  while (index--) {
+    if (
+      events[index][1].type === 'labelImage' &&
+      events[index][0] === 'enter'
+    ) {
+      events[index][1];
+      break
+    }
+  }
+
+  // Change the `labelImageMarker` to a `data`.
+  events[index + 1][1].type = 'data';
+  events[index + 3][1].type = 'gfmFootnoteCallLabelMarker'; // The whole (without `!`):
+
+  const call = {
+    type: 'gfmFootnoteCall',
+    start: Object.assign({}, events[index + 3][1].start),
+    end: Object.assign({}, events[events.length - 1][1].end)
+  }; // The `^` marker
+
+  const marker = {
+    type: 'gfmFootnoteCallMarker',
+    start: Object.assign({}, events[index + 3][1].end),
+    end: Object.assign({}, events[index + 3][1].end)
+  }; // Increment the end 1 character.
+
+  marker.end.column++;
+  marker.end.offset++;
+  marker.end._bufferIndex++;
+  const string = {
+    type: 'gfmFootnoteCallString',
+    start: Object.assign({}, marker.end),
+    end: Object.assign({}, events[events.length - 1][1].start)
+  };
+  const chunk = {
+    type: 'chunkString',
+    contentType: 'string',
+    start: Object.assign({}, string.start),
+    end: Object.assign({}, string.end)
+  };
+  /** @type {Event[]} */
+
+  const replacement = [
+    // Take the `labelImageMarker` (now `data`, the `!`)
+    events[index + 1],
+    events[index + 2],
+    ['enter', call, context], // The `[`
+    events[index + 3],
+    events[index + 4], // The `^`.
+    ['enter', marker, context],
+    ['exit', marker, context], // Everything in between.
+    ['enter', string, context],
+    ['enter', chunk, context],
+    ['exit', chunk, context],
+    ['exit', string, context], // The ending (`]`, properly parsed and labelled).
+    events[events.length - 2],
+    events[events.length - 1],
+    ['exit', call, context]
+  ];
+  events.splice(index, events.length - index + 1, ...replacement);
+  return events
 }
-/**
- * Normalize a URL (such as used in definitions).
- *
- * Encode unsafe characters with percent-encoding, skipping already encoded
- * sequences.
- *
- * @param {string} value
- * @returns {string}
- */
+/** @type {Tokenizer} */
 
-function normalizeUri(value) {
+function tokenizeGfmFootnoteCall(effects, ok, nok) {
+  const self = this;
   /** @type {string[]} */
-  const result = [];
-  let index = -1;
-  let start = 0;
-  let skip = 0;
+  // @ts-expect-error It’s fine!
 
-  while (++index < value.length) {
-    const code = value.charCodeAt(index);
-    /** @type {string} */
+  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
+  let size = 0;
+  /** @type {boolean} */
+
+  let data;
+  return start
+  /** @type {State} */
+
+  function start(code) {
+    effects.enter('gfmFootnoteCall');
+    effects.enter('gfmFootnoteCallLabelMarker');
+    effects.consume(code);
+    effects.exit('gfmFootnoteCallLabelMarker');
+    return callStart
+  }
+  /** @type {State} */
+
+  function callStart(code) {
+    if (code !== 94) return nok(code)
+    effects.enter('gfmFootnoteCallMarker');
+    effects.consume(code);
+    effects.exit('gfmFootnoteCallMarker');
+    effects.enter('gfmFootnoteCallString');
+    effects.enter('chunkString').contentType = 'string';
+    return callData
+  }
+  /** @type {State} */
+
+  function callData(code) {
+    /** @type {Token} */
+    let token;
+
+    if (code === null || code === 91 || size++ > 999) {
+      return nok(code)
+    }
+
+    if (code === 93) {
+      if (!data) {
+        return nok(code)
+      }
+
+      effects.exit('chunkString');
+      token = effects.exit('gfmFootnoteCallString');
+      return defined.includes(normalizeIdentifier(self.sliceSerialize(token)))
+        ? end(code)
+        : nok(code)
+    }
+
+    effects.consume(code);
+
+    if (!markdownLineEndingOrSpace(code)) {
+      data = true;
+    }
+
+    return code === 92 ? callEscape : callData
+  }
+  /** @type {State} */
+
+  function callEscape(code) {
+    if (code === 91 || code === 92 || code === 93) {
+      effects.consume(code);
+      size++;
+      return callData
+    }
+
+    return callData(code)
+  }
+  /** @type {State} */
+
+  function end(code) {
+    effects.enter('gfmFootnoteCallLabelMarker');
+    effects.consume(code);
+    effects.exit('gfmFootnoteCallLabelMarker');
+    effects.exit('gfmFootnoteCall');
+    return ok
+  }
+}
+/** @type {Tokenizer} */
+
+function tokenizeDefinitionStart(effects, ok, nok) {
+  const self = this;
+  /** @type {string[]} */
+  // @ts-expect-error It’s fine!
+
+  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
+  /** @type {string} */
+
+  let identifier;
+  let size = 0;
+  /** @type {boolean|undefined} */
+
+  let data;
+  return start
+  /** @type {State} */
+
+  function start(code) {
+    effects.enter('gfmFootnoteDefinition')._container = true;
+    effects.enter('gfmFootnoteDefinitionLabel');
+    effects.enter('gfmFootnoteDefinitionLabelMarker');
+    effects.consume(code);
+    effects.exit('gfmFootnoteDefinitionLabelMarker');
+    return labelStart
+  }
+  /** @type {State} */
+
+  function labelStart(code) {
+    if (code === 94) {
+      effects.enter('gfmFootnoteDefinitionMarker');
+      effects.consume(code);
+      effects.exit('gfmFootnoteDefinitionMarker');
+      effects.enter('gfmFootnoteDefinitionLabelString');
+      return atBreak
+    }
+
+    return nok(code)
+  }
+  /** @type {State} */
+
+  function atBreak(code) {
+    /** @type {Token} */
+    let token;
+
+    if (code === null || code === 91 || size > 999) {
+      return nok(code)
+    }
+
+    if (code === 93) {
+      if (!data) {
+        return nok(code)
+      }
 
-    let replace = ''; // A correct percent encoded value.
+      token = effects.exit('gfmFootnoteDefinitionLabelString');
+      identifier = normalizeIdentifier(self.sliceSerialize(token));
+      effects.enter('gfmFootnoteDefinitionLabelMarker');
+      effects.consume(code);
+      effects.exit('gfmFootnoteDefinitionLabelMarker');
+      effects.exit('gfmFootnoteDefinitionLabel');
+      return labelAfter
+    }
 
+    if (markdownLineEnding(code)) {
+      effects.enter('lineEnding');
+      effects.consume(code);
+      effects.exit('lineEnding');
+      size++;
+      return atBreak
+    }
+
+    effects.enter('chunkString').contentType = 'string';
+    return label(code)
+  }
+  /** @type {State} */
+
+  function label(code) {
     if (
-      code === 37 &&
-      asciiAlphanumeric(value.charCodeAt(index + 1)) &&
-      asciiAlphanumeric(value.charCodeAt(index + 2))
+      code === null ||
+      markdownLineEnding(code) ||
+      code === 91 ||
+      code === 93 ||
+      size > 999
     ) {
-      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.
-
-      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);
+      effects.exit('chunkString');
+      return atBreak(code)
     }
 
-    if (replace) {
-      result.push(value.slice(start, index), encodeURIComponent(replace));
-      start = index + skip + 1;
-      replace = '';
+    if (!markdownLineEndingOrSpace(code)) {
+      data = true;
     }
 
-    if (skip) {
-      index += skip;
-      skip = 0;
+    size++;
+    effects.consume(code);
+    return code === 92 ? labelEscape : label
+  }
+  /** @type {State} */
+
+  function labelEscape(code) {
+    if (code === 91 || code === 92 || code === 93) {
+      effects.consume(code);
+      size++;
+      return label
     }
+
+    return label(code)
   }
+  /** @type {State} */
 
-  return result.join('') + value.slice(start)
-}
+  function labelAfter(code) {
+    if (code === 58) {
+      effects.enter('definitionMarker');
+      effects.consume(code);
+      effects.exit('definitionMarker'); // Any whitespace after the marker is eaten, forming indented code
+      // is not possible.
+      // No space is also fine, just like a block quote marker.
 
-/**
- * @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} */
+      return factorySpace(effects, done, 'gfmFootnoteDefinitionWhitespace')
+    }
 
-const gfmAutolinkLiteralHtml = {
-  exit: {
-    literalAutolinkEmail,
-    literalAutolinkHttp,
-    literalAutolinkWww
+    return nok(code)
   }
-};
-/** @type {Handle} */
+  /** @type {State} */
 
-function literalAutolinkWww(token) {
-  anchorFromToken.call(this, token, 'http://');
-}
-/** @type {Handle} */
+  function done(code) {
+    if (!defined.includes(identifier)) {
+      defined.push(identifier);
+    }
 
-function literalAutolinkEmail(token) {
-  anchorFromToken.call(this, token, 'mailto:');
+    return ok(code)
+  }
 }
-/** @type {Handle} */
+/** @type {Tokenizer} */
 
-function literalAutolinkHttp(token) {
-  anchorFromToken.call(this, token);
+function tokenizeDefinitionContinuation(effects, ok, nok) {
+  // Either a blank line, which is okay, or an indented thing.
+  return effects.check(blankLine, ok, effects.attempt(indent, ok, nok))
 }
-/**
- * @this CompileContext
- * @param {Token} token
- * @param {string} [protocol]
- * @returns {void}
- */
+/** @type {Exiter} */
 
-function anchorFromToken(token, protocol) {
-  const url = this.sliceSerialize(token);
-  this.tag('<a href="' + sanitizeUri((protocol || '') + url) + '">');
-  this.raw(this.encode(url));
-  this.tag('</a>');
+function gfmFootnoteDefinitionEnd(effects) {
+  effects.exit('gfmFootnoteDefinition');
 }
+/** @type {Tokenizer} */
 
-/**
- * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
- */
+function tokenizeIndent(effects, ok, nok) {
+  const self = this;
+  return factorySpace(
+    effects,
+    afterPrefix,
+    'gfmFootnoteDefinitionIndent',
+    4 + 1
+  )
+  /** @type {State} */
 
-/** @type {HtmlExtension} */
-const gfmStrikethroughHtml = {
-  enter: {
-    strikethrough() {
-      this.tag('<del>');
-    }
-  },
-  exit: {
-    strikethrough() {
-      this.tag('</del>');
-    }
+  function afterPrefix(code) {
+    const tail = self.events[self.events.length - 1];
+    return tail &&
+      tail[1].type === 'gfmFootnoteDefinitionIndent' &&
+      tail[2].sliceSerialize(tail[1], true).length === 4
+      ? ok(code)
+      : nok(code)
   }
-};
+}
 
 /**
  * @typedef {import('micromark-util-types').Extension} Extension
@@ -14835,156 +15013,6 @@ function gfmStrikethrough(options = {}) {
   }
 }
 
-/**
- * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
- */
-
-/**
- * @typedef {import('./syntax.js').Align} Align
- */
-const alignment = {
-  null: '',
-  left: ' align="left"',
-  right: ' align="right"',
-  center: ' align="center"'
-};
-/** @type {HtmlExtension} */
-
-const gfmTableHtml = {
-  enter: {
-    table(token) {
-      this.lineEndingIfNeeded();
-      this.tag('<table>'); // @ts-expect-error Custom.
-
-      this.setData('tableAlign', token._align);
-    },
-
-    tableBody() {
-      // Clear slurping line ending from the delimiter row.
-      this.setData('slurpOneLineEnding');
-      this.tag('<tbody>');
-    },
-
-    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('<td' + align + '>');
-      }
-    },
-
-    tableHead() {
-      this.lineEndingIfNeeded();
-      this.tag('<thead>');
-    },
-
-    tableHeader() {
-      this.lineEndingIfNeeded();
-      this.tag(
-        '<th' + // @ts-expect-error Custom.
-          alignment[this.getData('tableAlign')[this.getData('tableColumn')]] +
-          '>'
-      );
-    },
-
-    tableRow() {
-      this.setData('tableColumn', 0);
-      this.lineEndingIfNeeded();
-      this.tag('<tr>');
-    }
-  },
-  exit: {
-    // Overwrite the default code text data handler to unescape escaped pipes when
-    // they are in tables.
-    codeTextData(token) {
-      let value = this.sliceSerialize(token);
-
-      if (this.getData('tableAlign')) {
-        value = value.replace(/\\([\\|])/g, replace$1);
-      }
-
-      this.raw(this.encode(value));
-    },
-
-    table() {
-      this.setData('tableAlign'); // If there was no table body, make sure the slurping from the delimiter row
-      // is cleared.
-
-      this.setData('slurpAllLineEndings');
-      this.lineEndingIfNeeded();
-      this.tag('</table>');
-    },
-
-    tableBody() {
-      this.lineEndingIfNeeded();
-      this.tag('</tbody>');
-    },
-
-    tableData() {
-      /** @type {number} */
-      // @ts-expect-error Custom.
-      const column = this.getData('tableColumn'); // @ts-expect-error Custom.
-
-      if (column in this.getData('tableAlign')) {
-        this.tag('</td>');
-        this.setData('tableColumn', column + 1);
-      } else {
-        // Stop capturing.
-        this.resume();
-      }
-    },
-
-    tableHead() {
-      this.lineEndingIfNeeded();
-      this.tag('</thead>');
-      this.setData('slurpOneLineEnding', true); // Slurp the line ending from the delimiter row.
-    },
-
-    tableHeader() {
-      this.tag('</th>'); // @ts-expect-error Custom.
-
-      this.setData('tableColumn', this.getData('tableColumn') + 1);
-    },
-
-    tableRow() {
-      /** @type {Align[]} */
-      // @ts-expect-error Custom.
-      const align = this.getData('tableAlign');
-      /** @type {number} */
-      // @ts-expect-error Custom.
-
-      let column = this.getData('tableColumn');
-
-      while (column < align.length) {
-        this.lineEndingIfNeeded(); // @ts-expect-error `null` is fine as an index.
-
-        this.tag('<td' + alignment[align[column]] + '></td>');
-        column++;
-      }
-
-      this.setData('tableColumn', column);
-      this.lineEndingIfNeeded();
-      this.tag('</tr>');
-    }
-  }
-};
-/**
- * @param {string} $0
- * @param {string} $1
- * @returns {string}
- */
-
-function replace$1($0, $1) {
-  // Pipes work, backslashes don’t (but can’t escape pipes).
-  return $1 === '|' ? $1 : $0
-}
-
 /**
  * @typedef {import('micromark-util-types').Extension} Extension
  * @typedef {import('micromark-util-types').Resolver} Resolver
@@ -15601,74 +15629,6 @@ function tokenizeNextPrefixedOrBlank(effects, ok, nok) {
   }
 }
 
-/**
- * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
- * @typedef {import('micromark-util-types').Token} Token
- * @typedef {import('micromark-util-types').CompileContext} CompileContext
- */
-
-/**
- * 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;
-
-/**
- * As HTML (text) parses tags separately (and v. strictly), we don’t need to be
- * global.
- */
-const reText = new RegExp('^' + reFlow.source, 'i');
-
-/** @type {HtmlExtension} */
-const gfmTagfilterHtml = {
-  exit: {
-    htmlFlowData(token) {
-      exitHtmlData.call(this, token, reFlow);
-    },
-    htmlTextData(token) {
-      exitHtmlData.call(this, token, reText);
-    }
-  }
-};
-
-/**
- * @this {CompileContext}
- * @param {Token} token
- * @param {RegExp} filter
- */
-function exitHtmlData(token, filter) {
-  let value = this.sliceSerialize(token);
-
-  if (this.options.allowDangerousHtml) {
-    value = value.replace(filter, '&lt;$1$2');
-  }
-
-  this.raw(this.encode(value));
-}
-
-/**
- * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
- */
-
-/** @type {HtmlExtension} */
-const gfmTaskListItemHtml = {
-  enter: {
-    taskListCheck() {
-      this.tag('<input ');
-    }
-  },
-  exit: {
-    taskListCheck() {
-      this.tag('disabled="" type="checkbox">');
-    },
-
-    taskListCheckValueChecked() {
-      this.tag('checked="" ');
-    }
-  }
-};
-
 /**
  * @typedef {import('micromark-util-types').Extension} Extension
  * @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord
@@ -15770,6 +15730,7 @@ function spaceThenNonSpace(effects, ok, nok) {
  * @typedef {import('micromark-util-types').Extension} Extension
  * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
  * @typedef {import('micromark-extension-gfm-strikethrough').Options} Options
+ * @typedef {import('micromark-extension-gfm-footnote').HtmlOptions} HtmlOptions
  */
 
 /**
@@ -15781,21 +15742,13 @@ function spaceThenNonSpace(effects, ok, nok) {
 function gfm(options) {
   return combineExtensions([
     gfmAutolinkLiteral,
+    gfmFootnote(),
     gfmStrikethrough(options),
     gfmTable,
     gfmTaskListItem
   ])
 }
 
-/** @type {HtmlExtension} */
-combineHtmlExtensions([
-  gfmAutolinkLiteralHtml,
-  gfmStrikethroughHtml,
-  gfmTableHtml,
-  gfmTagfilterHtml,
-  gfmTaskListItemHtml
-]);
-
 /**
  * Get the total count of `character` in `value`.
  *
@@ -16454,6 +16407,174 @@ function previous(match, email) {
   )
 }
 
+/**
+ * @typedef {import('mdast').FootnoteReference} FootnoteReference
+ * @typedef {import('mdast').FootnoteDefinition} FootnoteDefinition
+ * @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').Map} Map
+ */
+
+let warningColonInFootnote = false;
+let warningListInFootnote = false;
+
+/**
+ * @returns {FromMarkdownExtension}
+ */
+function gfmFootnoteFromMarkdown() {
+  return {
+    enter: {
+      gfmFootnoteDefinition: enterFootnoteDefinition,
+      gfmFootnoteDefinitionLabelString: enterFootnoteDefinitionLabelString,
+      gfmFootnoteCall: enterFootnoteCall,
+      gfmFootnoteCallString: enterFootnoteCallString
+    },
+    exit: {
+      gfmFootnoteDefinition: exitFootnoteDefinition,
+      gfmFootnoteDefinitionLabelString: exitFootnoteDefinitionLabelString,
+      gfmFootnoteCall: exitFootnoteCall,
+      gfmFootnoteCallString: exitFootnoteCallString
+    }
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function enterFootnoteDefinition(token) {
+    this.enter(
+      {type: 'footnoteDefinition', identifier: '', label: '', children: []},
+      token
+    );
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function enterFootnoteDefinitionLabelString() {
+    this.buffer();
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function exitFootnoteDefinitionLabelString(token) {
+    const label = this.resume();
+    const node = /** @type {FootnoteDefinition} */ (
+      this.stack[this.stack.length - 1]
+    );
+    node.label = label;
+    node.identifier = normalizeIdentifier(
+      this.sliceSerialize(token)
+    ).toLowerCase();
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function exitFootnoteDefinition(token) {
+    this.exit(token);
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function enterFootnoteCall(token) {
+    this.enter({type: 'footnoteReference', identifier: '', label: ''}, token);
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function enterFootnoteCallString() {
+    this.buffer();
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function exitFootnoteCallString(token) {
+    const label = this.resume();
+    const node = /** @type {FootnoteDefinition} */ (
+      this.stack[this.stack.length - 1]
+    );
+    node.label = label;
+    node.identifier = normalizeIdentifier(
+      this.sliceSerialize(token)
+    ).toLowerCase();
+  }
+
+  /** @type {FromMarkdownHandle} */
+  function exitFootnoteCall(token) {
+    this.exit(token);
+  }
+}
+
+/**
+ * @returns {ToMarkdownExtension}
+ */
+function gfmFootnoteToMarkdown() {
+  footnoteReference.peek = footnoteReferencePeek;
+
+  return {
+    // This is on by default already.
+    unsafe: [{character: '[', inConstruct: ['phrasing', 'label', 'reference']}],
+    handlers: {footnoteDefinition, footnoteReference}
+  }
+
+  /**
+   * @type {ToMarkdownHandle}
+   * @param {FootnoteReference} node
+   */
+  function footnoteReference(node, _, context) {
+    const exit = context.enter('footnoteReference');
+    const subexit = context.enter('reference');
+    const reference = safe(context, association(node), {
+      before: '^',
+      after: ']'
+    });
+    subexit();
+    exit();
+    return '[^' + reference + ']'
+  }
+
+  /** @type {ToMarkdownHandle} */
+  function footnoteReferencePeek() {
+    return '['
+  }
+
+  /**
+   * @type {ToMarkdownHandle}
+   * @param {FootnoteDefinition} node
+   */
+  function footnoteDefinition(node, _, context) {
+    const exit = context.enter('footnoteDefinition');
+    const subexit = context.enter('label');
+    const id = safe(context, association(node), {before: '^', after: ']'});
+    const label = '[^' + id + ']:';
+    subexit();
+    const value = indentLines(containerFlow(node, context), map);
+    exit();
+
+    if (!warningColonInFootnote && id.includes(':')) {
+      console.warn(
+        '[mdast-util-gfm-footnote] Warning: Found a colon in footnote identifier `' +
+          id +
+          '`. GitHub currently crahes on colons in footnotes (see <https://github.com/github/cmark-gfm/issues/241> for more info)'
+      );
+      warningColonInFootnote = true;
+    }
+
+    if (!warningListInFootnote) {
+      visit$1(node, 'list', () => {
+        console.warn(
+          '[mdast-util-gfm-footnote] Warning: Found a list in a footnote definition. GitHub currently crahes on lists in footnotes (see <https://github.com/github/cmark-gfm/issues/241> for more info)'
+        );
+        warningListInFootnote = true;
+        return EXIT$1
+      });
+    }
+
+    return value
+
+    /** @type {Map} */
+    function map(line, index, blank) {
+      if (index) {
+        return (blank ? '' : '    ') + line
+      }
+
+      return (blank ? label : label + ' ') + line
+    }
+  }
+}
+
 /**
  * @typedef {import('mdast').Delete} Delete
  * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
@@ -17110,14 +17231,17 @@ function listItemWithTaskListItem(node, parent, context) {
  */
 
 /**
- * @type {Array.<FromMarkdownExtension>}
+ * @returns {Array.<FromMarkdownExtension>}
  */
-const gfmFromMarkdown = [
-  gfmAutolinkLiteralFromMarkdown,
-  gfmStrikethroughFromMarkdown,
-  gfmTableFromMarkdown,
-  gfmTaskListItemFromMarkdown
-];
+function gfmFromMarkdown() {
+  return [
+    gfmAutolinkLiteralFromMarkdown,
+    gfmFootnoteFromMarkdown(),
+    gfmStrikethroughFromMarkdown,
+    gfmTableFromMarkdown,
+    gfmTaskListItemFromMarkdown
+  ]
+}
 
 /**
  * @param {Options} [options]
@@ -17127,6 +17251,7 @@ function gfmToMarkdown(options) {
   return {
     extensions: [
       gfmAutolinkLiteralToMarkdown,
+      gfmFootnoteToMarkdown(),
       gfmStrikethroughToMarkdown,
       gfmTableToMarkdown(options),
       gfmTaskListItemToMarkdown
@@ -17148,7 +17273,7 @@ function remarkGfm(options = {}) {
   const data = this.data();
 
   add('micromarkExtensions', gfm(options));
-  add('fromMarkdownExtensions', gfmFromMarkdown);
+  add('fromMarkdownExtensions', gfmFromMarkdown());
   add('toMarkdownExtensions', gfmToMarkdown(options));
 
   /**
@@ -27397,8 +27522,17 @@ function testProhibited (val, content) {
   }
 
   let regexpString = '(?<!\\.|@[a-zA-Z0-9/-]*)';
-
-  const ignoreNextTo = val.ignoreNextTo ? escapeStringRegexp(val.ignoreNextTo) : '';
+  let ignoreNextTo;
+  if (val.ignoreNextTo) {
+    if (Array.isArray(val.ignoreNextTo)) {
+      const parts = val.ignoreNextTo.map(a => escapeStringRegexp(a)).join('|');
+      ignoreNextTo = `(?:${parts})`;
+    } else {
+      ignoreNextTo = escapeStringRegexp(val.ignoreNextTo);
+    }
+  } else {
+    ignoreNextTo = '';
+  }
   const replaceCaptureGroups = !!val.replaceCaptureGroups;
 
   // If it starts with a letter, make sure it is a word break.
@@ -28211,12 +28345,6 @@ var remarkLintUnorderedListMarkerStyle$1 = remarkLintUnorderedListMarkerStyle;
 
 // @see https://github.com/nodejs/node/blob/HEAD/doc/guides/doc-style-guide.md
 
-// 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"
-  );
-
 // Add in rules alphabetically after Gfm and PresetLintRecommended.
 const plugins = [
   remarkGfm,
diff --git a/tools/lint-md/package-lock.json b/tools/lint-md/package-lock.json
index 4525157c8bf8af..75e4ea906e72b5 100644
--- a/tools/lint-md/package-lock.json
+++ b/tools/lint-md/package-lock.json
@@ -9,8 +9,8 @@
       "version": "1.0.0",
       "dependencies": {
         "remark-parse": "^10.0.0",
-        "remark-preset-lint-node": "^3.0.1",
-        "remark-stringify": "^10.0.0",
+        "remark-preset-lint-node": "^3.3.0",
+        "remark-stringify": "^10.0.1",
         "to-vfile": "^7.2.2",
         "unified": "^10.1.0",
         "vfile-reporter": "^7.0.2"
@@ -621,11 +621,12 @@
       }
     },
     "node_modules/mdast-util-gfm": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-1.0.0.tgz",
-      "integrity": "sha512-JY4qImsTqivQ0Gl3qvdaizCpomFaNrHnjEhNjNNKeNEA5jZHAJDYu1+yO4V9jn4/ti8GrKdAScaT4F71knoxsA==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.0.tgz",
+      "integrity": "sha512-wMwejlTN3EQADPFuvxe8lmGsay3+f6gSJKdAHR6KBJzpcxvsjJSILB9K6u6G7eQLC7iOTyVIHYGui9uBc9r1Tg==",
       "dependencies": {
         "mdast-util-gfm-autolink-literal": "^1.0.0",
+        "mdast-util-gfm-footnote": "^1.0.0",
         "mdast-util-gfm-strikethrough": "^1.0.0",
         "mdast-util-gfm-table": "^1.0.0",
         "mdast-util-gfm-task-list-item": "^1.0.0"
@@ -650,6 +651,21 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/mdast-util-gfm-footnote": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.0.tgz",
+      "integrity": "sha512-qeg9YoS2YYP6OBmMyUFxKXb6BLwAsbGidIxgwDAXHIMYZQhIwe52L9BSJs+zP29Jp5nSERPkmG3tSwAN23/ZbQ==",
+      "dependencies": {
+        "@types/mdast": "^3.0.0",
+        "mdast-util-to-markdown": "^1.0.0",
+        "micromark-util-normalize-identifier": "^1.0.0",
+        "unist-util-visit": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/mdast-util-gfm-strikethrough": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.0.tgz",
@@ -808,11 +824,12 @@
       }
     },
     "node_modules/micromark-extension-gfm": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-1.0.0.tgz",
-      "integrity": "sha512-OjqbQPL1Vec/4l5hnC8WnMNmWwgrT9JvzR2udqIGrGKecZsdwY9GAWZ5482CuD12SXuHNj8aS8epni6ip0Pwog==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.0.tgz",
+      "integrity": "sha512-yYPlZ48Ss8fRFSmlQP/QXt3/M6tEvawEVFO+jDPnFA3mGeVgzIyaeHgrIV/9AMFAjQhctKA47Bk8xBhcuaL74Q==",
       "dependencies": {
         "micromark-extension-gfm-autolink-literal": "^1.0.0",
+        "micromark-extension-gfm-footnote": "^1.0.0",
         "micromark-extension-gfm-strikethrough": "^1.0.0",
         "micromark-extension-gfm-table": "^1.0.0",
         "micromark-extension-gfm-tagfilter": "^1.0.0",
@@ -841,6 +858,24 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/micromark-extension-gfm-footnote": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.2.tgz",
+      "integrity": "sha512-C6o+B7w1wDM4JjDJeHCTszFYF1q46imElNY6mfXsBfw4E91M9TvEEEt3sy0FbJmGVzdt1pqFVRYWT9ZZ0FjFuA==",
+      "dependencies": {
+        "micromark-core-commonmark": "^1.0.0",
+        "micromark-factory-space": "^1.0.0",
+        "micromark-util-character": "^1.0.0",
+        "micromark-util-normalize-identifier": "^1.0.0",
+        "micromark-util-sanitize-uri": "^1.0.0",
+        "micromark-util-symbol": "^1.0.0",
+        "uvu": "^0.5.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/micromark-extension-gfm-strikethrough": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.3.tgz",
@@ -1344,13 +1379,13 @@
       }
     },
     "node_modules/remark-gfm": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-2.0.0.tgz",
-      "integrity": "sha512-waIv4Tjcd2CTUDxKRYzuPyIHw1FoX4H2GjXAzXV9PxQWb+dU4fJivd/FZ+nxyzPARrqTjMIkwIwPoWNbpBhjcQ==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.0.tgz",
+      "integrity": "sha512-CXJw5h1iwUW6czFwi4tveoOSlsEZU44hcdNzUxC5uiNi7r/OQySf46AoEihM8/NwBbW1LcsnyGIsHBnbURFw2g==",
       "dependencies": {
         "@types/mdast": "^3.0.0",
-        "mdast-util-gfm": "^1.0.0",
-        "micromark-extension-gfm": "^1.0.0",
+        "mdast-util-gfm": "^2.0.0",
+        "micromark-extension-gfm": "^2.0.0",
         "unified": "^10.0.0"
       },
       "funding": {
@@ -1975,9 +2010,9 @@
       }
     },
     "node_modules/remark-lint-prohibited-strings": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/remark-lint-prohibited-strings/-/remark-lint-prohibited-strings-3.0.0.tgz",
-      "integrity": "sha512-Aw21KVeoOiDte6dNfeTfTgjKV19yWXpPjLxfJ3ShC22/r97gkGdOo4dnuwyEEAfKhr3uimtSf3rRQyGSudY5tQ==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/remark-lint-prohibited-strings/-/remark-lint-prohibited-strings-3.1.0.tgz",
+      "integrity": "sha512-zwfDDdYl7ye0gEHcwhdkv1ZGXj1ibw4gnLLZkkvySnDdTz2tshY3fOJLY5NikbVseaIRVGOr5qa+8J9WNQT/fA==",
       "dependencies": {
         "escape-string-regexp": "^5.0.0",
         "unified-lint-rule": "^2.0.0",
@@ -2099,12 +2134,12 @@
       }
     },
     "node_modules/remark-preset-lint-node": {
-      "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==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.3.0.tgz",
+      "integrity": "sha512-JPjXould+7VTpwj+YJHSoPiGwKLpmLAZJRveU/dT7mCDOdSSORe/SGo9fJDm6owUReg50b5AG2AY8nlReytHcA==",
       "dependencies": {
         "js-yaml": "^4.0.0",
-        "remark-gfm": "^2.0.0",
+        "remark-gfm": "^3.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",
@@ -2134,7 +2169,7 @@
         "remark-lint-table-cell-padding": "^4.0.0",
         "remark-lint-table-pipes": "^4.0.0",
         "remark-lint-unordered-list-marker-style": "^3.0.0",
-        "remark-preset-lint-recommended": "^6.0.0",
+        "remark-preset-lint-recommended": "^6.1.1",
         "semver": "^7.3.2",
         "unified-lint-rule": "^2.0.0",
         "unist-util-visit": "^4.1.0"
@@ -2172,9 +2207,9 @@
       }
     },
     "node_modules/remark-stringify": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.0.tgz",
-      "integrity": "sha512-3LAQqJ/qiUxkWc7fUcVuB7RtIT38rvmxfmJG8z1TiE/D8zi3JGQ2tTcTJu9Tptdpb7gFwU0whRi5q1FbFOb9yA==",
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.1.tgz",
+      "integrity": "sha512-380vOu9EHqRTDhI9RlPU2EKY1abUDEmxw9fW7pJ/8Jr1izk0UcdnZB30qiDDRYi6pGn5FnVf9Wd2iUeCWTqM7Q==",
       "dependencies": {
         "@types/mdast": "^3.0.0",
         "mdast-util-to-markdown": "^1.0.0",
@@ -3027,11 +3062,12 @@
       }
     },
     "mdast-util-gfm": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-1.0.0.tgz",
-      "integrity": "sha512-JY4qImsTqivQ0Gl3qvdaizCpomFaNrHnjEhNjNNKeNEA5jZHAJDYu1+yO4V9jn4/ti8GrKdAScaT4F71knoxsA==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.0.tgz",
+      "integrity": "sha512-wMwejlTN3EQADPFuvxe8lmGsay3+f6gSJKdAHR6KBJzpcxvsjJSILB9K6u6G7eQLC7iOTyVIHYGui9uBc9r1Tg==",
       "requires": {
         "mdast-util-gfm-autolink-literal": "^1.0.0",
+        "mdast-util-gfm-footnote": "^1.0.0",
         "mdast-util-gfm-strikethrough": "^1.0.0",
         "mdast-util-gfm-table": "^1.0.0",
         "mdast-util-gfm-task-list-item": "^1.0.0"
@@ -3048,6 +3084,17 @@
         "micromark-util-character": "^1.0.0"
       }
     },
+    "mdast-util-gfm-footnote": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.0.tgz",
+      "integrity": "sha512-qeg9YoS2YYP6OBmMyUFxKXb6BLwAsbGidIxgwDAXHIMYZQhIwe52L9BSJs+zP29Jp5nSERPkmG3tSwAN23/ZbQ==",
+      "requires": {
+        "@types/mdast": "^3.0.0",
+        "mdast-util-to-markdown": "^1.0.0",
+        "micromark-util-normalize-identifier": "^1.0.0",
+        "unist-util-visit": "^4.0.0"
+      }
+    },
     "mdast-util-gfm-strikethrough": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.0.tgz",
@@ -3158,11 +3205,12 @@
       }
     },
     "micromark-extension-gfm": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-1.0.0.tgz",
-      "integrity": "sha512-OjqbQPL1Vec/4l5hnC8WnMNmWwgrT9JvzR2udqIGrGKecZsdwY9GAWZ5482CuD12SXuHNj8aS8epni6ip0Pwog==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.0.tgz",
+      "integrity": "sha512-yYPlZ48Ss8fRFSmlQP/QXt3/M6tEvawEVFO+jDPnFA3mGeVgzIyaeHgrIV/9AMFAjQhctKA47Bk8xBhcuaL74Q==",
       "requires": {
         "micromark-extension-gfm-autolink-literal": "^1.0.0",
+        "micromark-extension-gfm-footnote": "^1.0.0",
         "micromark-extension-gfm-strikethrough": "^1.0.0",
         "micromark-extension-gfm-table": "^1.0.0",
         "micromark-extension-gfm-tagfilter": "^1.0.0",
@@ -3183,6 +3231,20 @@
         "uvu": "^0.5.0"
       }
     },
+    "micromark-extension-gfm-footnote": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.2.tgz",
+      "integrity": "sha512-C6o+B7w1wDM4JjDJeHCTszFYF1q46imElNY6mfXsBfw4E91M9TvEEEt3sy0FbJmGVzdt1pqFVRYWT9ZZ0FjFuA==",
+      "requires": {
+        "micromark-core-commonmark": "^1.0.0",
+        "micromark-factory-space": "^1.0.0",
+        "micromark-util-character": "^1.0.0",
+        "micromark-util-normalize-identifier": "^1.0.0",
+        "micromark-util-sanitize-uri": "^1.0.0",
+        "micromark-util-symbol": "^1.0.0",
+        "uvu": "^0.5.0"
+      }
+    },
     "micromark-extension-gfm-strikethrough": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.3.tgz",
@@ -3458,13 +3520,13 @@
       "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
     },
     "remark-gfm": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-2.0.0.tgz",
-      "integrity": "sha512-waIv4Tjcd2CTUDxKRYzuPyIHw1FoX4H2GjXAzXV9PxQWb+dU4fJivd/FZ+nxyzPARrqTjMIkwIwPoWNbpBhjcQ==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.0.tgz",
+      "integrity": "sha512-CXJw5h1iwUW6czFwi4tveoOSlsEZU44hcdNzUxC5uiNi7r/OQySf46AoEihM8/NwBbW1LcsnyGIsHBnbURFw2g==",
       "requires": {
         "@types/mdast": "^3.0.0",
-        "mdast-util-gfm": "^1.0.0",
-        "micromark-extension-gfm": "^1.0.0",
+        "mdast-util-gfm": "^2.0.0",
+        "micromark-extension-gfm": "^2.0.0",
         "unified": "^10.0.0"
       }
     },
@@ -3939,9 +4001,9 @@
       }
     },
     "remark-lint-prohibited-strings": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/remark-lint-prohibited-strings/-/remark-lint-prohibited-strings-3.0.0.tgz",
-      "integrity": "sha512-Aw21KVeoOiDte6dNfeTfTgjKV19yWXpPjLxfJ3ShC22/r97gkGdOo4dnuwyEEAfKhr3uimtSf3rRQyGSudY5tQ==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/remark-lint-prohibited-strings/-/remark-lint-prohibited-strings-3.1.0.tgz",
+      "integrity": "sha512-zwfDDdYl7ye0gEHcwhdkv1ZGXj1ibw4gnLLZkkvySnDdTz2tshY3fOJLY5NikbVseaIRVGOr5qa+8J9WNQT/fA==",
       "requires": {
         "escape-string-regexp": "^5.0.0",
         "unified-lint-rule": "^2.0.0",
@@ -4035,12 +4097,12 @@
       }
     },
     "remark-preset-lint-node": {
-      "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==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/remark-preset-lint-node/-/remark-preset-lint-node-3.3.0.tgz",
+      "integrity": "sha512-JPjXould+7VTpwj+YJHSoPiGwKLpmLAZJRveU/dT7mCDOdSSORe/SGo9fJDm6owUReg50b5AG2AY8nlReytHcA==",
       "requires": {
         "js-yaml": "^4.0.0",
-        "remark-gfm": "^2.0.0",
+        "remark-gfm": "^3.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",
@@ -4070,7 +4132,7 @@
         "remark-lint-table-cell-padding": "^4.0.0",
         "remark-lint-table-pipes": "^4.0.0",
         "remark-lint-unordered-list-marker-style": "^3.0.0",
-        "remark-preset-lint-recommended": "^6.0.0",
+        "remark-preset-lint-recommended": "^6.1.1",
         "semver": "^7.3.2",
         "unified-lint-rule": "^2.0.0",
         "unist-util-visit": "^4.1.0"
@@ -4101,9 +4163,9 @@
       }
     },
     "remark-stringify": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.0.tgz",
-      "integrity": "sha512-3LAQqJ/qiUxkWc7fUcVuB7RtIT38rvmxfmJG8z1TiE/D8zi3JGQ2tTcTJu9Tptdpb7gFwU0whRi5q1FbFOb9yA==",
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.1.tgz",
+      "integrity": "sha512-380vOu9EHqRTDhI9RlPU2EKY1abUDEmxw9fW7pJ/8Jr1izk0UcdnZB30qiDDRYi6pGn5FnVf9Wd2iUeCWTqM7Q==",
       "requires": {
         "@types/mdast": "^3.0.0",
         "mdast-util-to-markdown": "^1.0.0",
diff --git a/tools/lint-md/package.json b/tools/lint-md/package.json
index a6c883f9882c85..6b3e48c4671817 100644
--- a/tools/lint-md/package.json
+++ b/tools/lint-md/package.json
@@ -7,8 +7,8 @@
   },
   "dependencies": {
     "remark-parse": "^10.0.0",
-    "remark-preset-lint-node": "^3.0.1",
-    "remark-stringify": "^10.0.0",
+    "remark-preset-lint-node": "^3.3.0",
+    "remark-stringify": "^10.0.1",
     "to-vfile": "^7.2.2",
     "unified": "^10.1.0",
     "vfile-reporter": "^7.0.2"