Allow intra-word strikethrough in GFM (#313)

Allow intra-word strikethrough in GFM
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c15d30..daffada 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
 * **Breaking change:** Remove `ListSyntax.removeLeadingEmptyLine`,
   `ListSyntax.removeTrailingEmptyLines`, `TableSyntax.parseAlignments`,
   `TableSyntax.parseRow`.
+* Allow intra-word strikethrough in GFM
+  ([#300](https://github.com/dart-lang/markdown/issues/300)).
 
 ## 2.1.8
 
diff --git a/lib/src/inline_parser.dart b/lib/src/inline_parser.dart
index d1d0eaa..bd3c900 100644
--- a/lib/src/inline_parser.dart
+++ b/lib/src/inline_parser.dart
@@ -500,6 +500,7 @@
   final bool isRightFlanking;
   final bool isPrecededByPunctuation;
   final bool isFollowedByPunctuation;
+  final bool allowIntraWord;
 
   _DelimiterRun._({
     @required this.char,
@@ -508,9 +509,11 @@
     @required this.isRightFlanking,
     @required this.isPrecededByPunctuation,
     @required this.isFollowedByPunctuation,
+    @required this.allowIntraWord,
   });
 
-  static _DelimiterRun tryParse(InlineParser parser, int runStart, int runEnd) {
+  static _DelimiterRun tryParse(InlineParser parser, int runStart, int runEnd,
+      {bool allowIntraWord = false}) {
     bool leftFlanking,
         rightFlanking,
         precededByPunctuation,
@@ -538,7 +541,8 @@
     } else {
       leftFlanking = !followedByPunctuation ||
           whitespace.contains(preceding) ||
-          precededByPunctuation;
+          precededByPunctuation ||
+          allowIntraWord;
     }
 
     // http://spec.commonmark.org/0.28/#right-flanking-delimiter-run
@@ -547,7 +551,8 @@
     } else {
       rightFlanking = !precededByPunctuation ||
           whitespace.contains(following) ||
-          followedByPunctuation;
+          followedByPunctuation ||
+          allowIntraWord;
     }
 
     if (!leftFlanking && !rightFlanking) {
@@ -562,6 +567,7 @@
       isRightFlanking: rightFlanking,
       isPrecededByPunctuation: precededByPunctuation,
       isFollowedByPunctuation: followedByPunctuation,
+      allowIntraWord: allowIntraWord,
     );
   }
 
@@ -573,12 +579,18 @@
   // Whether a delimiter in this run can open emphasis or strong emphasis.
   bool get canOpen =>
       isLeftFlanking &&
-      (char == $asterisk || !isRightFlanking || isPrecededByPunctuation);
+      (char == $asterisk ||
+          !isRightFlanking ||
+          allowIntraWord ||
+          isPrecededByPunctuation);
 
   // Whether a delimiter in this run can close emphasis or strong emphasis.
   bool get canClose =>
       isRightFlanking &&
-      (char == $asterisk || !isLeftFlanking || isFollowedByPunctuation);
+      (char == $asterisk ||
+          !isLeftFlanking ||
+          allowIntraWord ||
+          isFollowedByPunctuation);
 }
 
 /// Matches syntax that has a pair of tags and becomes an element, like `*` for
@@ -592,6 +604,11 @@
   /// [emphasis delimiters]: http://spec.commonmark.org/0.28/#can-open-emphasis
   final bool requiresDelimiterRun;
 
+  /// Whether to allow intra-word delimiter runs. CommonMark emphasis and
+  /// strong emphasis does not allow this, but GitHub-Flavored Markdown allows
+  /// it on strikethrough.
+  final bool allowIntraWord;
+
   /// Create a new [TagSyntax] which matches text on [pattern].
   ///
   /// If [end] is passed, it is used as the pattern which denotes the end of
@@ -600,7 +617,10 @@
   /// emphasis delimiters.  If [startCharacter] is passed, it is used as a
   /// pre-matching check which is faster than matching against [pattern].
   TagSyntax(String pattern,
-      {String end, this.requiresDelimiterRun = false, int startCharacter})
+      {String end,
+      this.requiresDelimiterRun = false,
+      int startCharacter,
+      this.allowIntraWord = false})
       : endPattern = RegExp((end != null) ? end : pattern, multiLine: true),
         super(pattern, startCharacter: startCharacter);
 
@@ -614,7 +634,8 @@
       return true;
     }
 
-    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
+    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd,
+        allowIntraWord: allowIntraWord);
     if (delimiterRun != null && delimiterRun.canOpen) {
       parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun));
       return true;
@@ -629,7 +650,8 @@
     var matchStart = parser.pos;
     var matchEnd = parser.pos + runLength - 1;
     var openingRunLength = state.endPos - state.startPos;
-    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
+    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd,
+        allowIntraWord: allowIntraWord);
 
     if (openingRunLength == 1 && runLength == 1) {
       parser.addNode(Element('em', state.children));
@@ -665,14 +687,16 @@
 
 /// Matches strikethrough syntax according to the GFM spec.
 class StrikethroughSyntax extends TagSyntax {
-  StrikethroughSyntax() : super('~+', requiresDelimiterRun: true);
+  StrikethroughSyntax()
+      : super('~+', requiresDelimiterRun: true, allowIntraWord: true);
 
   @override
   bool onMatchEnd(InlineParser parser, Match match, TagState state) {
     var runLength = match.group(0).length;
     var matchStart = parser.pos;
     var matchEnd = parser.pos + runLength - 1;
-    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
+    var delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd,
+        allowIntraWord: allowIntraWord);
     if (!delimiterRun.isRightFlanking) {
       return false;
     }
@@ -1256,8 +1280,9 @@
     var openingRunLength = endPos - startPos;
     var closingMatchStart = parser.pos;
     var closingMatchEnd = parser.pos + runLength - 1;
-    var closingDelimiterRun =
-        _DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
+    var closingDelimiterRun = _DelimiterRun.tryParse(
+        parser, closingMatchStart, closingMatchEnd,
+        allowIntraWord: openingDelimiterRun.allowIntraWord);
     if (closingDelimiterRun != null && closingDelimiterRun.canClose) {
       // Emphasis rules #9 and #10:
       var oneRunOpensAndCloses =
diff --git a/test/extensions/strikethrough.unit b/test/extensions/strikethrough.unit
new file mode 100644
index 0000000..3599718
--- /dev/null
+++ b/test/extensions/strikethrough.unit
@@ -0,0 +1,20 @@
+>>> Missing leading whitespace
+word pas~~t~~ word
+<<<
+<p>word pas<del>t</del> word</p>
+>>> Missing trailing whitespace
+word ~~p~~ast word
+<<<
+<p>word <del>p</del>ast word</p>
+>>> Middle of word
+word p~~as~~t word
+<<<
+<p>word p<del>as</del>t word</p>
+>>> Whitespace after opening
+word~~ past~~ word
+<<<
+<p>word~~ past~~ word</p>
+>>> Whitespace before closing
+word ~~past ~~word
+<<<
+<p>word ~~past ~~word</p>
diff --git a/test/markdown_test.dart b/test/markdown_test.dart
index f4769c9..524c9b9 100644
--- a/test/markdown_test.dart
+++ b/test/markdown_test.dart
@@ -22,6 +22,8 @@
   // Inline syntax extensions
   testFile('extensions/emojis.unit', inlineSyntaxes: [EmojiSyntax()]);
   testFile('extensions/inline_html.unit', inlineSyntaxes: [InlineHtmlSyntax()]);
+  testFile('extensions/strikethrough.unit',
+      inlineSyntaxes: [StrikethroughSyntax()]);
 
   testDirectory('common_mark');
   testDirectory('gfm', extensionSet: ExtensionSet.gitHubFlavored);