Rewrite link reference definitions (dart-lang/markdown#506)

* Rewrite link reference definitions

* Update link_parser.dart

* Make some fields setter private

---------

Co-authored-by: Kevin Moore <kevmoo@google.com>
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index 2564759..675d8c7 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -11,6 +11,7 @@
 import 'block_syntaxes/header_syntax.dart';
 import 'block_syntaxes/horizontal_rule_syntax.dart';
 import 'block_syntaxes/html_block_syntax.dart';
+import 'block_syntaxes/link_reference_definition_syntax.dart';
 import 'block_syntaxes/ordered_list_syntax.dart';
 import 'block_syntaxes/paragraph_syntax.dart';
 import 'block_syntaxes/setext_header_syntax.dart';
@@ -62,6 +63,7 @@
     const HorizontalRuleSyntax(),
     const UnorderedListSyntax(),
     const OrderedListSyntax(),
+    const LinkReferenceDefinitionSyntax(),
     const ParagraphSyntax()
   ];
 
@@ -100,14 +102,21 @@
     return lines[_pos + linesAhead];
   }
 
+  /// Advances the reading position by one line.
   void advance() {
     _pos++;
   }
 
+  /// Retreats the reading position by one line.
   void retreat() {
     _pos--;
   }
 
+  /// Retreats the reading position by [count] lines.
+  void retreatBy(int count) {
+    _pos -= count;
+  }
+
   bool get isDone => _pos >= lines.length;
 
   /// Gets whether or not the current line matches the given pattern.
@@ -166,7 +175,9 @@
           }
           neverMatch = _pos != positionBefore ? null : syntax;
 
-          if (block != null || syntax is EmptyBlockSyntax) {
+          if (block != null ||
+              syntax is EmptyBlockSyntax ||
+              syntax is LinkReferenceDefinitionSyntax) {
             _start = _pos;
           }
 
diff --git a/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart
new file mode 100644
index 0000000..5b2b1b5
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../ast.dart';
+import '../block_parser.dart';
+import '../document.dart';
+import '../line.dart';
+import '../link_parser.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+
+class LinkReferenceDefinitionSyntax extends BlockSyntax {
+  @override
+  RegExp get pattern => linkReferenceDefinitionPattern;
+
+  @override
+  bool canEndBlock(BlockParser parser) => false;
+
+  const LinkReferenceDefinitionSyntax();
+
+  @override
+  Node? parse(BlockParser parser) {
+    final lines = <Line>[parser.current];
+    parser.advance();
+
+    while (!BlockSyntax.isAtBlockEnd(parser)) {
+      lines.add(parser.current);
+      parser.advance();
+    }
+
+    if (!_parseLinkReferenceDefinition(lines, parser)) {
+      parser.retreatBy(lines.length);
+    }
+
+    return null;
+  }
+
+  bool _parseLinkReferenceDefinition(List<Line> lines, BlockParser parser) {
+    final linkParser = LinkParser(lines.map((e) => e.content).join('\n'))
+      ..parseDefinition();
+
+    if (!linkParser.valid) {
+      return false;
+    }
+
+    // Retreat the parsing position back to where the link reference definition
+    // ends, so that the next syntax can continue parsing from there.
+    parser.retreatBy(linkParser.unconsumedLines);
+
+    final labelString = normalizeLinkLabel(linkParser.label!);
+
+    parser.document.linkReferences.putIfAbsent(
+      labelString,
+      () => LinkReference(
+        labelString,
+        linkParser.destination!,
+        linkParser.title,
+      ),
+    );
+
+    return true;
+  }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
index affdefd..53d496d 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
@@ -4,18 +4,12 @@
 
 import '../ast.dart';
 import '../block_parser.dart';
-import '../document.dart';
 import '../patterns.dart';
-import '../util.dart';
 import 'block_syntax.dart';
 import 'setext_header_syntax.dart';
 
 /// Parses paragraphs of regular text.
 class ParagraphSyntax extends BlockSyntax {
-  static final _reflinkDefinitionStart = RegExp(r'[ ]{0,3}\[');
-
-  static final _whitespacePattern = RegExp(r'^\s*$');
-
   @override
   RegExp get pattern => dummyPattern;
 
@@ -49,146 +43,7 @@
       return null;
     }
 
-    final paragraphLines = _extractReflinkDefinitions(parser, childLines);
-    if (paragraphLines == null) {
-      // Paragraph consisted solely of reference link definitions.
-      return Text('');
-    } else {
-      final contents = UnparsedContent(paragraphLines.join('\n').trimRight());
-      return Element('p', [contents]);
-    }
-  }
-
-  /// Extract reference link definitions from the front of the paragraph, and
-  /// return the remaining paragraph lines.
-  List<String>? _extractReflinkDefinitions(
-    BlockParser parser,
-    List<String> lines,
-  ) {
-    bool lineStartsReflinkDefinition(int i) =>
-        lines[i].startsWith(_reflinkDefinitionStart);
-
-    var i = 0;
-    loopOverDefinitions:
-    while (true) {
-      // Check for reflink definitions.
-      if (!lineStartsReflinkDefinition(i)) {
-        // It's paragraph content from here on out.
-        break;
-      }
-      var contents = lines[i];
-      var j = i + 1;
-      while (j < lines.length) {
-        // Check to see if the _next_ line might start a new reflink definition.
-        // Even if it turns out not to be, but it started with a '[', then it
-        // is not a part of _this_ possible reflink definition.
-        if (lineStartsReflinkDefinition(j)) {
-          // Try to parse [contents] as a reflink definition.
-          if (_parseReflinkDefinition(parser, contents)) {
-            // Loop again, starting at the next possible reflink definition.
-            i = j;
-            continue loopOverDefinitions;
-          } else {
-            // Could not parse [contents] as a reflink definition.
-            break;
-          }
-        } else {
-          contents = '$contents\n${lines[j]}';
-          j++;
-        }
-      }
-      // End of the block.
-      if (_parseReflinkDefinition(parser, contents)) {
-        i = j;
-        break;
-      }
-
-      // It may be that there is a reflink definition starting at [i], but it
-      // does not extend all the way to [j], such as:
-      //
-      //     [link]: url     // line i
-      //     "title"
-      //     garbage
-      //     [link2]: url   // line j
-      //
-      // In this case, [i, i+1] is a reflink definition, and the rest is
-      // paragraph content.
-      while (j >= i) {
-        // This isn't the most efficient loop, what with this big ole'
-        // Iterable allocation (`getRange`) followed by a big 'ole String
-        // allocation, but we
-        // must walk backwards, checking each range.
-        contents = lines.getRange(i, j).join('\n');
-        if (_parseReflinkDefinition(parser, contents)) {
-          // That is the last reflink definition. The rest is paragraph
-          // content.
-          i = j;
-          break;
-        }
-        j--;
-      }
-      // The ending was not a reflink definition at all. Just paragraph
-      // content.
-
-      break;
-    }
-
-    if (i == lines.length) {
-      // No paragraph content.
-      return null;
-    } else {
-      // Ends with paragraph content.
-      return lines.sublist(i);
-    }
-  }
-
-  // Parse [contents] as a reference link definition.
-  //
-  // Also adds the reference link definition to the document.
-  //
-  // Returns whether [contents] could be parsed as a reference link definition.
-  bool _parseReflinkDefinition(BlockParser parser, String contents) {
-    final pattern = RegExp(
-      // Leading indentation.
-      '''^[ ]{0,3}'''
-      // Reference id in brackets, and URL.
-      r'''\[((?:\\\]|[^\]])+)\]:\s*(?:<(\S+)>|(\S+))\s*'''
-      // Title in double or single quotes, or parens.
-      r'''("[^"]+"|'[^']+'|\([^)]+\)|)\s*$''',
-      multiLine: true,
-    );
-    final match = pattern.firstMatch(contents);
-    if (match == null) {
-      // Not a reference link definition.
-      return false;
-    }
-    if (match.match.length < contents.length) {
-      // Trailing text. No good.
-      return false;
-    }
-
-    var label = match[1]!;
-    final destination = match[2] ?? match[3]!;
-    var title = match[4];
-
-    // The label must contain at least one non-whitespace character.
-    if (_whitespacePattern.hasMatch(label)) {
-      return false;
-    }
-
-    if (title == '') {
-      // No title.
-      title = null;
-    } else {
-      // Remove "", '', or ().
-      title = title!.substring(1, title.length - 1);
-    }
-
-    // References are case-insensitive, and internal whitespace is compressed.
-    label = normalizeLinkLabel(label);
-
-    parser.document.linkReferences
-        .putIfAbsent(label, () => LinkReference(label, destination, title));
-    return true;
+    final contents = UnparsedContent(childLines.join('\n').trimRight());
+    return Element('p', [contents]);
   }
 }
diff --git a/pkgs/markdown/lib/src/link_parser.dart b/pkgs/markdown/lib/src/link_parser.dart
new file mode 100644
index 0000000..4db22ad
--- /dev/null
+++ b/pkgs/markdown/lib/src/link_parser.dart
@@ -0,0 +1,270 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'charcode.dart';
+import 'text_parser.dart';
+import 'util.dart';
+
+class LinkParser extends TextParser {
+  /// If there is a valid link formed.
+  bool get valid => _valid;
+  bool _valid = false;
+
+  /// Link label.
+  String? get label => _label;
+  String? _label;
+
+  /// Link destination.
+  String? get destination => _destination;
+  String? _destination;
+
+  /// Link title.
+  String? get title => _title;
+  String? _title;
+
+  LinkParser(super.source);
+
+  /// How many lines of the [source] have been consumed by link reference
+  /// definition.
+  int get unconsumedLines => _unconsumedLines;
+  int _unconsumedLines = 0;
+
+  /// Parses [source] to a link reference definition.
+  void parseDefinition() {
+    if (!parseLabel() || isDone || charAt() != $colon) {
+      return;
+    }
+
+    // Advance to the next character after the colon.
+    advance();
+    if (!_parseDestination()) {
+      return;
+    }
+
+    var precedingWhitespaces = moveThroughWhitespace();
+    if (isDone) {
+      _valid = true;
+      return;
+    }
+
+    final multiline = charAt() == $lf;
+    precedingWhitespaces += moveThroughWhitespace(multiLine: true);
+
+    // The title must be preceded by whitespaces.
+    if (precedingWhitespaces == 0 || isDone) {
+      _valid = isDone;
+      return;
+    }
+
+    final hasValidTitle = _parseTitle();
+    // For example: `[foo]: <bar> "baz` is a invalid definition, but this one is
+    // valid:
+    // ```
+    // [foo]: <bar>
+    // "baz
+    // ```
+    if (!hasValidTitle && !multiline) {
+      return;
+    }
+
+    if (hasValidTitle) {
+      moveThroughWhitespace();
+      if (!isDone && charAt() != $lf) {
+        // It is not a valid definition if the title is followed by
+        // non-whitespace characters, for example: `[foo]: <bar> "baz" hello`.
+        // See https://spec.commonmark.org/0.30/#example-209.
+        if (!multiline) {
+          return;
+        }
+        // But it is a valid link reference definition if this definition is
+        // multiline, see https://spec.commonmark.org/0.30/#example-210.
+        _title = null;
+      }
+    }
+
+    final linesUnconsumed = source.substring(pos).split('\n');
+    if (linesUnconsumed.isNotEmpty && linesUnconsumed.first.isBlank) {
+      linesUnconsumed.removeAt(0);
+    }
+    _unconsumedLines = linesUnconsumed.length;
+
+    _valid = true;
+  }
+
+  /// Parses the link label, returns `true` if there is a valid link label.
+  bool parseLabel() {
+    moveThroughWhitespace(multiLine: true);
+
+    if (length - pos < 2) {
+      return false;
+    }
+
+    if (charAt() != $lbracket) {
+      return false;
+    }
+
+    // Advance past the opening `[`.
+    advance();
+    final start = pos;
+
+    // A link label can have at most 999 characters inside the square brackets.
+    // See https://spec.commonmark.org/0.30/#link-label.
+    var maxLoop = 999;
+    while (true) {
+      if (maxLoop-- < 0) {
+        return false;
+      }
+      final char = charAt(pos);
+      if (char == $backslash) {
+        advance();
+      } else if (char == $lbracket) {
+        return false;
+      } else if (char == $rbracket) {
+        break;
+      }
+      advance();
+      if (isDone) {
+        return false;
+      }
+    }
+
+    final text = substring(start, pos);
+    if (text.isBlank) {
+      return false;
+    }
+
+    // Advance past the closing `]`.
+    advance();
+    _label = text;
+    return true;
+  }
+
+  /// Parses the link destination, returns `true` there is a valid link
+  /// destination.
+  bool _parseDestination() {
+    moveThroughWhitespace(multiLine: true);
+    if (isDone) {
+      return false;
+    }
+
+    final isValidDestination = charAt() == $lt
+        ? _parseBracketedDestination()
+        : _parseBareDestination();
+
+    return isValidDestination;
+  }
+
+  /// Parses bracketed destinations (destinations wrapped in `<...>`). The
+  /// current position of the parser must be the first character of the
+  /// destination.
+  ///
+  /// Returns `true` if there is a valid link destination.
+  bool _parseBracketedDestination() {
+    // Walk past the opening `<`.
+    advance();
+
+    final start = pos;
+    while (true) {
+      final char = charAt();
+      if (char == $backslash) {
+        advance();
+      } else if (char == $lf || char == $cr || char == $ff) {
+        return false;
+      } else if (char == $gt) {
+        break;
+      }
+      advance();
+      if (isDone) {
+        return false;
+      }
+    }
+
+    _destination = substring(start, pos);
+
+    // Advance past the closing `>`.
+    advance();
+    return true;
+  }
+
+  /// Parse "bare" destinations (destinations _not_ wrapped in `<...>`). The
+  /// current position of the parser must be the first character of the
+  /// destination.
+  ///
+  /// Returns `true` if there is a valid link destination.
+  bool _parseBareDestination() {
+    var parenCount = 0;
+    final start = pos;
+
+    while (true) {
+      final char = charAt();
+      if (char == $backslash) {
+        advance();
+      } else if (char == $space || char == $lf || char == $cr || char == $ff) {
+        break;
+      } else if (char == $lparen) {
+        parenCount++;
+      } else if (char == $rparen) {
+        parenCount--;
+        if (parenCount == 0) {
+          advance();
+          break;
+        }
+      }
+      advance();
+
+      // There is no ending delimiter, so `isDone` also means it is at the end
+      // of a link destination.
+      if (isDone) {
+        break;
+      }
+    }
+
+    _destination = substring(start, pos);
+    return true;
+  }
+
+  /// Parses the **optional** link title, returns `true` if there is a valid
+  /// link title.
+  bool _parseTitle() {
+    // See: https://spec.commonmark.org/0.30/#link-title
+    // The whitespace should be followed by a title delimiter.
+    final delimiter = charAt();
+    if (delimiter != $apostrophe &&
+        delimiter != $quote &&
+        delimiter != $lparen) {
+      return false;
+    }
+
+    final closeDelimiter = delimiter == $lparen ? $rparen : delimiter;
+    advance();
+    if (isDone) {
+      return false;
+    }
+    final start = pos;
+
+    // Looking for an un-escaped closing delimiter.
+    while (true) {
+      final char = charAt();
+      if (char == $backslash) {
+        advance();
+      } else if (char == closeDelimiter) {
+        break;
+      }
+      advance();
+      if (isDone) {
+        return false;
+      }
+    }
+
+    if (isDone) {
+      return false;
+    }
+
+    _title = substring(start, pos);
+
+    // Advance past the closing delimiter.
+    advance();
+    return true;
+  }
+}
diff --git a/pkgs/markdown/lib/src/patterns.dart b/pkgs/markdown/lib/src/patterns.dart
index 03e618c..1fdddfc 100644
--- a/pkgs/markdown/lib/src/patterns.dart
+++ b/pkgs/markdown/lib/src/patterns.dart
@@ -142,3 +142,6 @@
   '&(?:([a-z0-9]+)|#([0-9]{1,7})|#x([a-f0-9]{1,6}));',
   caseSensitive: false,
 );
+
+/// A line starts with `[`.
+final linkReferenceDefinitionPattern = RegExp(r'[ ]{0,3}\[');
diff --git a/pkgs/markdown/test/common_mark/link_reference_definitions.unit b/pkgs/markdown/test/common_mark/link_reference_definitions.unit
index 8d9ae8f..0677259 100644
--- a/pkgs/markdown/test/common_mark/link_reference_definitions.unit
+++ b/pkgs/markdown/test/common_mark/link_reference_definitions.unit
@@ -25,10 +25,7 @@
 
 [Foo bar]
 <<<
-<p>[Foo bar]:
-<my url>
-'title'</p>
-<p>[Foo bar]</p>
+<p><a href="my%20url" title="title">Foo bar</a></p>
 >>> Link reference definitions - 196
 [foo]: /url '
 title
@@ -72,20 +69,20 @@
 
 [foo]
 <<<
-<p><a href="%3C%3E">foo</a></p>
+<p><a href="">foo</a></p>
 >>> Link reference definitions - 201
 [foo]: <bar>(baz)
 
 [foo]
 <<<
-<p><a href="bar" title="baz">foo</a></p>
+<p>[foo]: <bar>(baz)</p>
+<p>[foo]</p>
 >>> Link reference definitions - 202
 [foo]: /url\bar\*baz "foo\"bar\baz"
 
 [foo]
 <<<
-<p>[foo]: /url\bar*baz &quot;foo&quot;bar\baz&quot;</p>
-<p>[foo]</p>
+<p><a href="/url%5Cbar*baz" title="foo&quot;bar\baz">foo</a></p>
 >>> Link reference definitions - 203
 [foo]
 
@@ -173,16 +170,15 @@
 ===
 [foo]
 <<<
-<h1>[foo]: /url
-bar</h1>
-<p>[foo]</p>
+<h1>bar</h1>
+<p><a href="/url">foo</a></p>
 >>> Link reference definitions - 216
 [foo]: /url
 ===
 [foo]
 <<<
-<h1>[foo]: /url</h1>
-<p>[foo]</p>
+<p>===
+<a href="/url">foo</a></p>
 >>> Link reference definitions - 217
 [foo]: /foo-url "foo"
 [bar]: /bar-url
diff --git a/pkgs/markdown/test/common_mark/links.unit b/pkgs/markdown/test/common_mark/links.unit
index 4015350..1cc94bd 100644
--- a/pkgs/markdown/test/common_mark/links.unit
+++ b/pkgs/markdown/test/common_mark/links.unit
@@ -322,6 +322,7 @@
 [ref[]: /uri
 <<<
 <p>[foo][ref[]</p>
+<p>[ref[]: /uri</p>
 >>> Links - 546
 [foo][ref[bar]]
 
diff --git a/pkgs/markdown/test/common_mark/lists.unit b/pkgs/markdown/test/common_mark/lists.unit
index 986f2aa..e0422fd 100644
--- a/pkgs/markdown/test/common_mark/lists.unit
+++ b/pkgs/markdown/test/common_mark/lists.unit
@@ -256,7 +256,8 @@
 <p>a</p>
 </li>
 <li>
-<p>b</p></li>
+<p>b</p>
+</li>
 <li>
 <p>d</p>
 </li>
diff --git a/pkgs/markdown/test/gfm/link_reference_definitions.unit b/pkgs/markdown/test/gfm/link_reference_definitions.unit
index 442c431..b09236f 100644
--- a/pkgs/markdown/test/gfm/link_reference_definitions.unit
+++ b/pkgs/markdown/test/gfm/link_reference_definitions.unit
@@ -25,10 +25,7 @@
 
 [Foo bar]
 <<<
-<p>[Foo bar]:
-<my url>
-'title'</p>
-<p>[Foo bar]</p>
+<p><a href="my%20url" title="title">Foo bar</a></p>
 >>> Link reference definitions - 165
 [foo]: /url '
 title
@@ -72,20 +69,20 @@
 
 [foo]
 <<<
-<p><a href="%3C%3E">foo</a></p>
+<p><a href="">foo</a></p>
 >>> Link reference definitions - 170
 [foo]: <bar>(baz)
 
 [foo]
 <<<
-<p><a href="bar" title="baz">foo</a></p>
+<p>[foo]: <bar>(baz)</p>
+<p>[foo]</p>
 >>> Link reference definitions - 171
 [foo]: /url\bar\*baz "foo\"bar\baz"
 
 [foo]
 <<<
-<p>[foo]: /url\bar*baz &quot;foo&quot;bar\baz&quot;</p>
-<p>[foo]</p>
+<p><a href="/url%5Cbar*baz" title="foo&quot;bar\baz">foo</a></p>
 >>> Link reference definitions - 172
 [foo]
 
@@ -173,16 +170,15 @@
 ===
 [foo]
 <<<
-<h1>[foo]: /url
-bar</h1>
-<p>[foo]</p>
+<h1>bar</h1>
+<p><a href="/url">foo</a></p>
 >>> Link reference definitions - 185
 [foo]: /url
 ===
 [foo]
 <<<
-<h1>[foo]: /url</h1>
-<p>[foo]</p>
+<p>===
+<a href="/url">foo</a></p>
 >>> Link reference definitions - 186
 [foo]: /foo-url "foo"
 [bar]: /bar-url
diff --git a/pkgs/markdown/test/gfm/links.unit b/pkgs/markdown/test/gfm/links.unit
index 38a861e..7288c79 100644
--- a/pkgs/markdown/test/gfm/links.unit
+++ b/pkgs/markdown/test/gfm/links.unit
@@ -310,6 +310,7 @@
 [ref[]: /uri
 <<<
 <p>[foo][ref[]</p>
+<p>[ref[]: /uri</p>
 >>> Links - 555
 [foo][ref[bar]]
 
diff --git a/pkgs/markdown/test/gfm/lists.unit b/pkgs/markdown/test/gfm/lists.unit
index 80558b7..bc675e5 100644
--- a/pkgs/markdown/test/gfm/lists.unit
+++ b/pkgs/markdown/test/gfm/lists.unit
@@ -256,7 +256,8 @@
 <p>a</p>
 </li>
 <li>
-<p>b</p></li>
+<p>b</p>
+</li>
 <li>
 <p>d</p>
 </li>
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index d009416..b3d17a9 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -400,14 +400,14 @@
   "192": "strict",
   "193": "strict",
   "194": "strict",
-  "195": "fail",
+  "195": "strict",
   "196": "strict",
   "197": "strict",
   "198": "strict",
   "199": "strict",
-  "200": "fail",
-  "201": "fail",
-  "202": "fail",
+  "200": "strict",
+  "201": "strict",
+  "202": "strict",
   "203": "strict",
   "204": "strict",
   "205": "strict",
@@ -420,8 +420,8 @@
   "212": "strict",
   "213": "strict",
   "214": "strict",
-  "215": "fail",
-  "216": "fail",
+  "215": "strict",
+  "216": "strict",
   "217": "strict",
   "218": "strict"
  },
@@ -490,7 +490,7 @@
   "542": "strict",
   "543": "strict",
   "544": "strict",
-  "545": "fail",
+  "545": "strict",
   "546": "strict",
   "547": "strict",
   "548": "strict",
@@ -584,7 +584,7 @@
   "314": "strict",
   "315": "strict",
   "316": "strict",
-  "317": "loose",
+  "317": "strict",
   "318": "strict",
   "319": "strict",
   "320": "strict",
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index 4bd7bda..faf5757 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -12,8 +12,8 @@
   22 of   22 – 100.0%  Images
   12 of   12 – 100.0%  Indented code blocks
    1 of    1 – 100.0%  Inlines
-  21 of   27 –  77.8%  Link reference definitions
-  89 of   90 –  98.9%  Links
+  27 of   27 – 100.0%  Link reference definitions
+  90 of   90 – 100.0%  Links
   48 of   48 – 100.0%  List items
   26 of   26 – 100.0%  Lists
    8 of    8 – 100.0%  Paragraphs
@@ -24,5 +24,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 645 of  652 –  98.9%  TOTAL
- 627 of  645 –  97.2%  TOTAL Strict
+ 652 of  652 – 100.0%  TOTAL
+ 635 of  652 –  97.4%  TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index 8e9f23c..7b5f993 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -415,14 +415,14 @@
   "161": "strict",
   "162": "strict",
   "163": "strict",
-  "164": "fail",
+  "164": "strict",
   "165": "strict",
   "166": "strict",
   "167": "strict",
   "168": "strict",
-  "169": "fail",
-  "170": "fail",
-  "171": "fail",
+  "169": "strict",
+  "170": "strict",
+  "171": "strict",
   "172": "strict",
   "173": "strict",
   "174": "strict",
@@ -435,8 +435,8 @@
   "181": "strict",
   "182": "strict",
   "183": "strict",
-  "184": "fail",
-  "185": "fail",
+  "184": "strict",
+  "185": "strict",
   "186": "strict",
   "187": "strict",
   "188": "loose"
@@ -503,7 +503,7 @@
   "551": "strict",
   "552": "strict",
   "553": "strict",
-  "554": "fail",
+  "554": "strict",
   "555": "strict",
   "556": "strict",
   "557": "strict",
@@ -597,7 +597,7 @@
   "294": "strict",
   "295": "strict",
   "296": "strict",
-  "297": "loose",
+  "297": "strict",
   "298": "strict",
   "299": "strict",
   "300": "strict",
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index e7ef10c..03a5fe1 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -14,8 +14,8 @@
   22 of   22 – 100.0%  Images
   12 of   12 – 100.0%  Indented code blocks
    1 of    1 – 100.0%  Inlines
-  22 of   28 –  78.6%  Link reference definitions
-  86 of   87 –  98.9%  Links
+  28 of   28 – 100.0%  Link reference definitions
+  87 of   87 – 100.0%  Links
   48 of   48 – 100.0%  List items
   26 of   26 – 100.0%  Lists
    8 of    8 – 100.0%  Paragraphs
@@ -28,5 +28,5 @@
   11 of   11 – 100.0%  Tabs
    3 of    3 – 100.0%  Textual content
   19 of   19 – 100.0%  Thematic breaks
- 662 of  670 –  98.8%  TOTAL
- 641 of  662 –  96.8%  TOTAL Strict
+ 669 of  670 –  99.9%  TOTAL
+ 649 of  669 –  97.0%  TOTAL Strict