Improve FencedCodeBlockSyntax (dart-lang/markdown#478)
* Improve FencedCodeBlockSyntax
* Update gfm_stats.txt
* Fix some requests
* run tool/stats.dart --update-files
diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
index 0b55f4a..f9713ef 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
@@ -4,7 +4,6 @@
import '../ast.dart';
import '../block_parser.dart';
-import '../charcode.dart';
import '../patterns.dart';
import '../util.dart';
import 'block_syntax.dart';
@@ -19,30 +18,62 @@
const FencedCodeBlockSyntax();
@override
- bool canParse(BlockParser parser) {
- final match = pattern.firstMatch(parser.current);
- if (match == null) return false;
- final codeFence = match.group(1)!;
- final infoString = match.group(2);
- // From the CommonMark spec:
- //
- // > If the info string comes after a backtick fence, it may not contain
- // > any backtick characters.
- return codeFence.codeUnitAt(0) != $backquote ||
- !infoString!.codeUnits.contains($backquote);
+ Node parse(BlockParser parser) {
+ final openingFence = _FenceMatch.fromMatch(pattern.firstMatch(
+ escapePunctuation(parser.current),
+ )!);
+
+ var text = parseChildLines(
+ parser,
+ openingFence.marker,
+ openingFence.indent,
+ ).join('\n');
+
+ if (parser.document.encodeHtml) {
+ text = escapeHtml(text);
+ }
+ if (text.isNotEmpty) {
+ text = '$text\n';
+ }
+
+ final code = Element.text('code', text);
+ if (openingFence.hasLanguage) {
+ var language = decodeHtmlCharacters(openingFence.language);
+ if (parser.document.encodeHtml) {
+ language = escapeHtmlAttribute(language);
+ }
+ code.attributes['class'] = 'language-$language';
+ }
+
+ return Element('pre', [code]);
+ }
+
+ String _removeIndentation(String content, int length) {
+ final text = content.replaceFirst(RegExp('^\\s{0,$length}'), '');
+ return content.substring(content.length - text.length);
}
@override
- List<String> parseChildLines(BlockParser parser, [String? endBlock]) {
- endBlock ??= '';
-
+ List<String> parseChildLines(
+ BlockParser parser, [
+ String openingMarker = '',
+ int indent = 0,
+ ]) {
final childLines = <String>[];
+
parser.advance();
+ _FenceMatch? closingFence;
while (!parser.isDone) {
final match = pattern.firstMatch(parser.current);
- if (match == null || !match[1]!.startsWith(endBlock)) {
- childLines.add(parser.current);
+ closingFence = match == null ? null : _FenceMatch.fromMatch(match);
+
+ // Closing code fences cannot have info strings:
+ // https://spec.commonmark.org/0.30/#example-147
+ if (closingFence == null ||
+ !closingFence.marker.startsWith(openingMarker) ||
+ closingFence.hasInfo) {
+ childLines.add(_removeIndentation(parser.current, indent));
parser.advance();
} else {
parser.advance();
@@ -50,46 +81,55 @@
}
}
+ // https://spec.commonmark.org/0.30/#example-127
+ // https://spec.commonmark.org/0.30/#example-128
+ if (closingFence == null && childLines.last.trim().isEmpty) {
+ childLines.removeLast();
+ }
+
return childLines;
}
+}
- @override
- Node parse(BlockParser parser) {
- // Get the syntax identifier, if there is one.
- final match = pattern.firstMatch(parser.current)!;
- final endBlock = match.group(1);
- var infoString = match.group(2)!;
+class _FenceMatch {
+ _FenceMatch._({
+ required this.indent,
+ required this.marker,
+ required this.info,
+ });
- final childLines = parseChildLines(parser, endBlock);
+ factory _FenceMatch.fromMatch(RegExpMatch match) {
+ String marker;
+ String info;
- // The Markdown tests expect a trailing newline.
- childLines.add('');
-
- var text = childLines.join('\n');
- if (parser.document.encodeHtml) {
- text = escapeHtml(text);
- }
- final code = Element.text('code', text);
-
- // the info-string should be trimmed
- // http://spec.commonmark.org/0.22/#example-100
- infoString = infoString.trim();
- if (infoString.isNotEmpty) {
- // only use the first word in the syntax
- // http://spec.commonmark.org/0.22/#example-100
- final firstSpace = infoString.indexOf(' ');
- if (firstSpace >= 0) {
- infoString = infoString.substring(0, firstSpace);
- }
- infoString = decodeHtmlCharacters(infoString);
- if (parser.document.encodeHtml) {
- infoString = escapeHtmlAttribute(infoString);
- }
- code.attributes['class'] = 'language-$infoString';
+ if (match.namedGroup('backtick') != null) {
+ marker = match.namedGroup('backtick')!;
+ info = match.namedGroup('backtickInfo')!;
+ } else {
+ marker = match.namedGroup('tilde')!;
+ info = match.namedGroup('tildeInfo')!;
}
- final element = Element('pre', [code]);
-
- return element;
+ return _FenceMatch._(
+ indent: match[1]!.length,
+ marker: marker,
+ info: info.trim(),
+ );
}
+
+ final int indent;
+ final String marker;
+
+ // The info-string should be trimmed,
+ // https://spec.commonmark.org/0.30/#info-string.
+ final String info;
+
+ // The first word of the info string is typically used to specify the language
+ // of the code sample,
+ // https://spec.commonmark.org/0.30/#example-143.
+ String get language => info.split(' ').first;
+
+ bool get hasInfo => info.isNotEmpty;
+
+ bool get hasLanguage => language.isNotEmpty;
}
diff --git a/pkgs/markdown/lib/src/patterns.dart b/pkgs/markdown/lib/src/patterns.dart
index c13600d..86531ba 100644
--- a/pkgs/markdown/lib/src/patterns.dart
+++ b/pkgs/markdown/lib/src/patterns.dart
@@ -21,7 +21,9 @@
final indentPattern = RegExp(r'^(?: | {0,3}\t)(.*)$');
/// Fenced code block.
-final codeFencePattern = RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$');
+final codeFencePattern = RegExp(
+ r'^([ ]{0,3})(?:(?<backtick>`{3,})(?<backtickInfo>[^`]*)|(?<tilde>~{3,})(?<tildeInfo>.*))$',
+);
/// Fenced blockquotes.
final blockquoteFencePattern = RegExp(r'^>{3}\s*$');
@@ -199,6 +201,10 @@
'(?<condition_7>(?:$namedTagDefinition)\\s*\$))',
caseSensitive: false);
+/// ASCII punctuation characters.
+// see https://spec.commonmark.org/0.30/#unicode-whitespace-character.
+const asciiPunctuationCharacters = r'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~''';
+
/// A pattern to match HTML entity references and numeric character references.
// https://spec.commonmark.org/0.30/#entity-and-numeric-character-references
final htmlCharactersPattern = RegExp(
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart
index 05ec697..a4861e2 100644
--- a/pkgs/markdown/lib/src/util.dart
+++ b/pkgs/markdown/lib/src/util.dart
@@ -147,3 +147,20 @@
/// Returns the whole match String
String get match => this[0]!;
}
+
+/// Escapes the ASCII punctuation characters after backslash(`\`).
+String escapePunctuation(String input) {
+ final buffer = StringBuffer();
+
+ for (var i = 0; i < input.length; i++) {
+ if (input.codeUnitAt(i) == $backslash) {
+ final next = i + 1 < input.length ? input[i + 1] : null;
+ if (next != null && asciiPunctuationCharacters.contains(next)) {
+ i++;
+ }
+ }
+ buffer.write(input[i]);
+ }
+
+ return buffer.toString();
+}
diff --git a/pkgs/markdown/test/common_mark/backslash_escapes.unit b/pkgs/markdown/test/common_mark/backslash_escapes.unit
index 9b794df..7042aa8 100644
--- a/pkgs/markdown/test/common_mark/backslash_escapes.unit
+++ b/pkgs/markdown/test/common_mark/backslash_escapes.unit
@@ -75,5 +75,5 @@
foo
```
<<<
-<pre><code class="language-foo\+bar">foo
+<pre><code class="language-foo+bar">foo
</code></pre>
diff --git a/pkgs/markdown/test/common_mark/block_quotes.unit b/pkgs/markdown/test/common_mark/block_quotes.unit
index 84559d7..f223442 100644
--- a/pkgs/markdown/test/common_mark/block_quotes.unit
+++ b/pkgs/markdown/test/common_mark/block_quotes.unit
@@ -96,8 +96,7 @@
<pre><code>foo
</code></pre>
</blockquote>
-<pre><code>
-</code></pre>
+<pre><code></code></pre>
>>> Block quotes - 238
> foo
- bar
diff --git a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit
index af54076..6148ae3 100644
--- a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit
+++ b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit
@@ -61,8 +61,7 @@
>>> Fenced code blocks - 126
```
<<<
-<pre><code>
-</code></pre>
+<pre><code></code></pre>
>>> Fenced code blocks - 127
`````
@@ -72,7 +71,6 @@
<pre><code>
```
aaa
-
</code></pre>
>>> Fenced code blocks - 128
> ```
@@ -105,7 +103,7 @@
aaa
```
<<<
-<pre><code> aaa
+<pre><code>aaa
aaa
</code></pre>
>>> Fenced code blocks - 132
@@ -116,7 +114,7 @@
```
<<<
<pre><code>aaa
- aaa
+aaa
aaa
</code></pre>
>>> Fenced code blocks - 133
@@ -126,9 +124,9 @@
aaa
```
<<<
-<pre><code> aaa
- aaa
- aaa
+<pre><code>aaa
+ aaa
+aaa
</code></pre>
>>> Fenced code blocks - 134
```
@@ -160,7 +158,6 @@
<<<
<pre><code>aaa
```
-
</code></pre>
>>> Fenced code blocks - 138
``` ```
@@ -175,7 +172,6 @@
<<<
<pre><code>aaa
~~~ ~~
-
</code></pre>
>>> Fenced code blocks - 140
foo
@@ -245,6 +241,5 @@
``` aaa
```
<<<
-<pre><code></code></pre>
-<pre><code>
+<pre><code>``` aaa
</code></pre>
diff --git a/pkgs/markdown/test/common_mark/lists.unit b/pkgs/markdown/test/common_mark/lists.unit
index e105c3b..7eb42df 100644
--- a/pkgs/markdown/test/common_mark/lists.unit
+++ b/pkgs/markdown/test/common_mark/lists.unit
@@ -279,7 +279,6 @@
</li>
</ul>
<pre><code>- c
-
</code></pre>
>>> Lists - 319
- a
diff --git a/pkgs/markdown/test/gfm/backslash_escapes.unit b/pkgs/markdown/test/gfm/backslash_escapes.unit
index e0f5dfb..f5cce5c 100644
--- a/pkgs/markdown/test/gfm/backslash_escapes.unit
+++ b/pkgs/markdown/test/gfm/backslash_escapes.unit
@@ -75,5 +75,5 @@
foo
```
<<<
-<pre><code class="language-foo\+bar">foo
+<pre><code class="language-foo+bar">foo
</code></pre>
diff --git a/pkgs/markdown/test/gfm/block_quotes.unit b/pkgs/markdown/test/gfm/block_quotes.unit
index 5c45af4..30e1ef9 100644
--- a/pkgs/markdown/test/gfm/block_quotes.unit
+++ b/pkgs/markdown/test/gfm/block_quotes.unit
@@ -96,8 +96,7 @@
<pre><code>foo
</code></pre>
</blockquote>
-<pre><code>
-</code></pre>
+<pre><code></code></pre>
>>> Block quotes - 216
> foo
- bar
diff --git a/pkgs/markdown/test/gfm/fenced_code_blocks.unit b/pkgs/markdown/test/gfm/fenced_code_blocks.unit
index 11ab448..dd13ca0 100644
--- a/pkgs/markdown/test/gfm/fenced_code_blocks.unit
+++ b/pkgs/markdown/test/gfm/fenced_code_blocks.unit
@@ -61,8 +61,7 @@
>>> Fenced code blocks - 96
```
<<<
-<pre><code>
-</code></pre>
+<pre><code></code></pre>
>>> Fenced code blocks - 97
`````
@@ -72,7 +71,6 @@
<pre><code>
```
aaa
-
</code></pre>
>>> Fenced code blocks - 98
> ```
@@ -105,7 +103,7 @@
aaa
```
<<<
-<pre><code> aaa
+<pre><code>aaa
aaa
</code></pre>
>>> Fenced code blocks - 102
@@ -116,7 +114,7 @@
```
<<<
<pre><code>aaa
- aaa
+aaa
aaa
</code></pre>
>>> Fenced code blocks - 103
@@ -126,9 +124,9 @@
aaa
```
<<<
-<pre><code> aaa
- aaa
- aaa
+<pre><code>aaa
+ aaa
+aaa
</code></pre>
>>> Fenced code blocks - 104
```
@@ -160,7 +158,6 @@
<<<
<pre><code>aaa
```
-
</code></pre>
>>> Fenced code blocks - 108
``` ```
@@ -175,7 +172,6 @@
<<<
<pre><code>aaa
~~~ ~~
-
</code></pre>
>>> Fenced code blocks - 110
foo
@@ -245,6 +241,5 @@
``` aaa
```
<<<
-<pre><code></code></pre>
-<pre><code>
+<pre><code>``` aaa
</code></pre>
diff --git a/pkgs/markdown/test/gfm/lists.unit b/pkgs/markdown/test/gfm/lists.unit
index 6a6e847..9911dea 100644
--- a/pkgs/markdown/test/gfm/lists.unit
+++ b/pkgs/markdown/test/gfm/lists.unit
@@ -279,7 +279,6 @@
</li>
</ul>
<pre><code>- c
-
</code></pre>
>>> Lists - 299
- a
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index 7c98634..47935d8 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -53,7 +53,7 @@
"21": "strict",
"22": "strict",
"23": "strict",
- "24": "fail"
+ "24": "strict"
},
"Blank lines": {
"227": "strict"
@@ -269,20 +269,20 @@
"123": "strict",
"124": "strict",
"125": "strict",
- "126": "loose",
- "127": "loose",
+ "126": "strict",
+ "127": "strict",
"128": "strict",
"129": "strict",
"130": "strict",
- "131": "loose",
- "132": "loose",
- "133": "loose",
+ "131": "strict",
+ "132": "strict",
+ "133": "strict",
"134": "strict",
"135": "strict",
"136": "strict",
- "137": "loose",
+ "137": "strict",
"138": "loose",
- "139": "loose",
+ "139": "strict",
"140": "strict",
"141": "strict",
"142": "strict",
@@ -290,7 +290,7 @@
"144": "strict",
"145": "strict",
"146": "strict",
- "147": "fail"
+ "147": "strict"
},
"Hard line breaks": {
"633": "strict",
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index f1200e9..27d1105 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -1,12 +1,12 @@
17 of 18 – 94.4% ATX headings
19 of 19 – 100.0% Autolinks
- 12 of 13 – 92.3% Backslash escapes
+ 13 of 13 – 100.0% Backslash escapes
1 of 1 – 100.0% Blank lines
23 of 25 – 92.0% Block quotes
22 of 22 – 100.0% Code spans
130 of 131 – 99.2% Emphasis and strong emphasis
15 of 17 – 88.2% Entity and numeric character references
- 28 of 29 – 96.6% Fenced code blocks
+ 29 of 29 – 100.0% Fenced code blocks
15 of 15 – 100.0% Hard line breaks
44 of 44 – 100.0% HTML blocks
21 of 22 – 95.5% Images
@@ -24,5 +24,5 @@
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 622 of 652 – 95.4% TOTAL
- 562 of 622 – 90.4% TOTAL Strict
+ 624 of 652 – 95.7% TOTAL
+ 571 of 624 – 91.5% TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index c37aada..01e91ab 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -66,7 +66,7 @@
"317": "strict",
"318": "strict",
"319": "strict",
- "320": "fail"
+ "320": "strict"
},
"Blank lines": {
"197": "strict"
@@ -285,20 +285,20 @@
"93": "strict",
"94": "strict",
"95": "strict",
- "96": "loose",
- "97": "loose",
+ "96": "strict",
+ "97": "strict",
"98": "strict",
"99": "strict",
"100": "strict",
- "101": "loose",
- "102": "loose",
- "103": "loose",
+ "101": "strict",
+ "102": "strict",
+ "103": "strict",
"104": "strict",
"105": "strict",
"106": "strict",
- "107": "loose",
+ "107": "strict",
"108": "loose",
- "109": "loose",
+ "109": "strict",
"110": "strict",
"111": "strict",
"112": "strict",
@@ -306,7 +306,7 @@
"114": "strict",
"115": "strict",
"116": "strict",
- "117": "fail"
+ "117": "strict"
},
"Hard line breaks": {
"654": "strict",
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index 5b47d05..eff6bbc 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -1,14 +1,14 @@
17 of 18 – 94.4% ATX headings
19 of 19 – 100.0% Autolinks
11 of 11 – 100.0% Autolinks (extension)
- 12 of 13 – 92.3% Backslash escapes
+ 13 of 13 – 100.0% Backslash escapes
1 of 1 – 100.0% Blank lines
23 of 25 – 92.0% Block quotes
22 of 22 – 100.0% Code spans
0 of 1 – 0.0% Disallowed Raw HTML (extension)
130 of 131 – 99.2% Emphasis and strong emphasis
15 of 17 – 88.2% Entity and numeric character references
- 28 of 29 – 96.6% Fenced code blocks
+ 29 of 29 – 100.0% Fenced code blocks
15 of 15 – 100.0% Hard line breaks
43 of 43 – 100.0% HTML blocks
21 of 22 – 95.5% Images
@@ -28,5 +28,5 @@
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 640 of 671 – 95.4% TOTAL
- 578 of 640 – 90.3% TOTAL Strict
+ 642 of 671 – 95.7% TOTAL
+ 587 of 642 – 91.4% TOTAL Strict