Clean up:

1. Use "var" for locals.
2. Don't use type parameters on temporary collections.
3. Be more strict about null.
4. Reformat.

R=sethladd@google.com

Review URL: https://codereview.chromium.org//1274763005 .
diff --git a/pkgs/markdown/lib/src/ast.dart b/pkgs/markdown/lib/src/ast.dart
index 3290efb..6da7ab4 100644
--- a/pkgs/markdown/lib/src/ast.dart
+++ b/pkgs/markdown/lib/src/ast.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library markdown.ast;
+library markdown.src.ast;
 
 typedef Node Resolver(String name);
 
@@ -22,21 +22,21 @@
 
   Element.empty(this.tag)
       : children = null,
-        attributes = <String, String>{};
+        attributes = {};
 
   Element.withTag(this.tag)
       : children = [],
-        attributes = <String, String>{};
+        attributes = {};
 
   Element.text(this.tag, String text)
       : children = [new Text(text)],
-        attributes = <String, String>{};
+        attributes = {};
 
   bool get isEmpty => children == null;
 
   void accept(NodeVisitor visitor) {
     if (visitor.visitElementBefore(this)) {
-      for (final child in children) child.accept(visitor);
+      for (var child in children) child.accept(visitor);
       visitor.visitElementAfter(this);
     }
   }
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index ce92867..504fdbb 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -9,42 +9,42 @@
 import 'util.dart';
 
 /// The line contains only whitespace or is empty.
-final _RE_EMPTY = new RegExp(r'^([ \t]*)$');
+final _emptyPattern = new RegExp(r'^([ \t]*)$');
 
 /// A series of `=` or `-` (on the next line) define setext-style headers.
-final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$');
+final _setextPattern = new RegExp(r'^((=+)|(-+))$');
 
 /// Leading (and trailing) `#` define atx-style headers.
-final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$');
+final _headerPattern = new RegExp(r'^(#{1,6})(.*?)#*$');
 
 /// The line starts with `>` with one optional space after.
-final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
+final _blockquotePattern = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
 
 /// A line indented four spaces. Used for code blocks and lists.
-final _RE_INDENT = new RegExp(r'^(?:    |\t)(.*)$');
+final _indentPattern = new RegExp(r'^(?:    |\t)(.*)$');
 
 /// Fenced code block.
-final _RE_CODE = new RegExp(r'^(`{3,}|~{3,})(.*)$');
+final _codePattern = new RegExp(r'^(`{3,}|~{3,})(.*)$');
 
 /// Three or more hyphens, asterisks or underscores by themselves. Note that
 /// a line like `----` is valid as both HR and SETEXT. In case of a tie,
 /// SETEXT should win.
-final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|'
+final _hrPattern = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|'
     r'(_+[ ]{0,2}){3,}|'
     r'(\*+[ ]{0,2}){3,})$');
 
 /// Really hacky way to detect block-level embedded HTML. Just looks for
 /// "<somename".
-final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]');
+final _htmlPattern = new RegExp(r'^<[ ]*\w+[ >]');
 
 /// A line starting with one of these markers: `-`, `*`, `+`. May have up to
 /// three leading spaces before the marker and any number of spaces or tabs
 /// after.
-final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
+final _ulPattern = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
 
 /// A line starting with a number like `123.`. May have up to three leading
 /// spaces before the marker and any number of spaces or tabs after.
-final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
+final _olPattern = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
 
 /// Maintains the internal state needed to parse a series of lines into blocks
 /// of markdown suitable for further inline parsing.
@@ -120,10 +120,10 @@
 
   List<String> parseChildLines(BlockParser parser) {
     // Grab all of the lines that form the blockquote, stripping off the ">".
-    final childLines = <String>[];
+    var childLines = <String>[];
 
     while (!parser.isDone) {
-      final match = pattern.firstMatch(parser.current);
+      var match = pattern.firstMatch(parser.current);
       if (match == null) break;
       childLines.add(match[1]);
       parser.advance();
@@ -140,7 +140,7 @@
 }
 
 class EmptyBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_EMPTY;
+  RegExp get pattern => _emptyPattern;
 
   const EmptyBlockSyntax();
 
@@ -159,14 +159,14 @@
   bool canParse(BlockParser parser) {
     // Note: matches *next* line, not the current one. We're looking for the
     // underlining after this line.
-    return parser.matchesNext(_RE_SETEXT);
+    return parser.matchesNext(_setextPattern);
   }
 
   Node parse(BlockParser parser) {
-    final match = _RE_SETEXT.firstMatch(parser.next);
+    var match = _setextPattern.firstMatch(parser.next);
 
-    final tag = (match[1][0] == '=') ? 'h1' : 'h2';
-    final contents = parser.document.parseInline(parser.current);
+    var tag = (match[1][0] == '=') ? 'h1' : 'h2';
+    var contents = parser.document.parseInline(parser.current);
     parser.advance();
     parser.advance();
 
@@ -176,30 +176,30 @@
 
 /// Parses atx-style headers: `## Header ##`.
 class HeaderSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HEADER;
+  RegExp get pattern => _headerPattern;
 
   const HeaderSyntax();
 
   Node parse(BlockParser parser) {
-    final match = pattern.firstMatch(parser.current);
+    var match = pattern.firstMatch(parser.current);
     parser.advance();
-    final level = match[1].length;
-    final contents = parser.document.parseInline(match[2].trim());
+    var level = match[1].length;
+    var contents = parser.document.parseInline(match[2].trim());
     return new Element('h$level', contents);
   }
 }
 
 /// Parses email-style blockquotes: `> quote`.
 class BlockquoteSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_BLOCKQUOTE;
+  RegExp get pattern => _blockquotePattern;
 
   const BlockquoteSyntax();
 
   Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser);
+    var childLines = parseChildLines(parser);
 
     // Recursively parse the contents of the blockquote.
-    final children = parser.document.parseLines(childLines);
+    var children = parser.document.parseLines(childLines);
 
     return new Element('blockquote', children);
   }
@@ -207,12 +207,12 @@
 
 /// Parses preformatted code blocks that are indented four spaces.
 class CodeBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_INDENT;
+  RegExp get pattern => _indentPattern;
 
   const CodeBlockSyntax();
 
   List<String> parseChildLines(BlockParser parser) {
-    final childLines = <String>[];
+    var childLines = <String>[];
 
     while (!parser.isDone) {
       var match = pattern.firstMatch(parser.current);
@@ -238,30 +238,32 @@
   }
 
   Node parse(BlockParser parser) {
-    final childLines = parseChildLines(parser);
+    var childLines = parseChildLines(parser);
 
     // The Markdown tests expect a trailing newline.
     childLines.add('');
 
     // Escape the code.
-    final escaped = escapeHtml(childLines.join('\n'));
+    var escaped = escapeHtml(childLines.join('\n'));
 
     return new Element('pre', [new Element.text('code', escaped)]);
   }
 }
 
 /// Parses preformatted code blocks between two ~~~ or ``` sequences.
-/// [Pandoc's markdown documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html).
+///
+/// See [Pandoc's documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html).
 class FencedCodeBlockSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_CODE;
+  RegExp get pattern => _codePattern;
 
   const FencedCodeBlockSyntax();
 
   List<String> parseChildLines(BlockParser parser, [String endBlock]) {
     if (endBlock == null) endBlock = '';
 
-    final childLines = <String>[];
+    var childLines = <String>[];
     parser.advance();
+
     while (!parser.isDone) {
       var match = pattern.firstMatch(parser.current);
       if (match == null || !match[1].startsWith(endBlock)) {
@@ -272,6 +274,7 @@
         break;
       }
     }
+
     return childLines;
   }
 
@@ -281,25 +284,24 @@
     var endBlock = match.group(1);
     var syntax = match.group(2);
 
-    final childLines = parseChildLines(parser, endBlock);
+    var childLines = parseChildLines(parser, endBlock);
 
     // The Markdown tests expect a trailing newline.
     childLines.add('');
 
     // Escape the code.
-    final escaped = escapeHtml(childLines.join('\n'));
+    var escaped = escapeHtml(childLines.join('\n'));
 
     var element = new Element('pre', [new Element.text('code', escaped)]);
-    if (syntax != '') {
-      element.attributes['class'] = syntax;
-    }
+    if (syntax != '') element.attributes['class'] = syntax;
+
     return element;
   }
 }
 
 /// Parses horizontal rules like `---`, `_ _ _`, `*  *  *`, etc.
 class HorizontalRuleSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HR;
+  RegExp get pattern => _hrPattern;
 
   const HorizontalRuleSyntax();
 
@@ -320,17 +322,17 @@
 /// 3.  Absolutely no HTML parsing or validation is done. We're a markdown
 ///     parser not an HTML parser!
 class BlockHtmlSyntax extends BlockSyntax {
-  RegExp get pattern => _RE_HTML;
+  RegExp get pattern => _htmlPattern;
 
   bool get canEndBlock => false;
 
   const BlockHtmlSyntax();
 
   Node parse(BlockParser parser) {
-    final childLines = [];
+    var childLines = <String>[];
 
     // Eat until we hit a blank line.
-    while (!parser.isDone && !parser.matches(_RE_EMPTY)) {
+    while (!parser.isDone && !parser.matches(_emptyPattern)) {
       childLines.add(parser.current);
       parser.advance();
     }
@@ -355,7 +357,7 @@
   const ListSyntax();
 
   Node parse(BlockParser parser) {
-    final items = <ListItem>[];
+    var items = <ListItem>[];
     var childLines = <String>[];
 
     endItem() {
@@ -372,14 +374,14 @@
     }
 
     while (!parser.isDone) {
-      if (tryMatch(_RE_EMPTY)) {
+      if (tryMatch(_emptyPattern)) {
         // Add a blank line to the current list item.
         childLines.add('');
-      } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) {
+      } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
         // End the current list item and start a new one.
         endItem();
         childLines.add(match[1]);
-      } else if (tryMatch(_RE_INDENT)) {
+      } else if (tryMatch(_indentPattern)) {
         // Strip off indent and add to current item.
         childLines.add(match[1]);
       } else if (BlockSyntax.isAtBlockEnd(parser)) {
@@ -436,9 +438,9 @@
     // Remove any trailing empty lines and note which items are separated by
     // empty lines. Do this before seeing which items are single-line so that
     // trailing empty lines on the last item don't force it into being a block.
-    for (int i = 0; i < items.length; i++) {
-      for (int j = items[i].lines.length - 1; j > 0; j--) {
-        if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) {
+    for (var i = 0; i < items.length; i++) {
+      for (var j = items[i].lines.length - 1; j > 0; j--) {
+        if (_emptyPattern.firstMatch(items[i].lines[j]) != null) {
           // Found an empty line. Item and one after it are blocks.
           if (i < items.length - 1) {
             items[i].forceBlock = true;
@@ -452,22 +454,22 @@
     }
 
     // Convert the list items to Nodes.
-    final itemNodes = <Node>[];
-    for (final item in items) {
-      bool blockItem = item.forceBlock || (item.lines.length > 1);
+    var itemNodes = <Node>[];
+    for (var item in items) {
+      var blockItem = item.forceBlock || (item.lines.length > 1);
 
       // See if it matches some block parser.
-      final blocksInList = [
-        _RE_BLOCKQUOTE,
-        _RE_HEADER,
-        _RE_HR,
-        _RE_INDENT,
-        _RE_UL,
-        _RE_OL
+      var blocksInList = [
+        _blockquotePattern,
+        _headerPattern,
+        _hrPattern,
+        _indentPattern,
+        _ulPattern,
+        _olPattern
       ];
 
       if (!blockItem) {
-        for (final pattern in blocksInList) {
+        for (var pattern in blocksInList) {
           if (pattern.firstMatch(item.lines[0]) != null) {
             blockItem = true;
             break;
@@ -478,11 +480,11 @@
       // Parse the item as a block or inline.
       if (blockItem) {
         // Block list item.
-        final children = parser.document.parseLines(item.lines);
+        var children = parser.document.parseLines(item.lines);
         itemNodes.add(new Element('li', children));
       } else {
         // Raw list item.
-        final contents = parser.document.parseInline(item.lines[0]);
+        var contents = parser.document.parseInline(item.lines[0]);
         itemNodes.add(new Element('li', contents));
       }
     }
@@ -493,7 +495,7 @@
 
 /// Parses unordered lists.
 class UnorderedListSyntax extends ListSyntax {
-  RegExp get pattern => _RE_UL;
+  RegExp get pattern => _ulPattern;
   String get listTag => 'ul';
 
   const UnorderedListSyntax();
@@ -501,7 +503,7 @@
 
 /// Parses ordered lists.
 class OrderedListSyntax extends ListSyntax {
-  RegExp get pattern => _RE_OL;
+  RegExp get pattern => _olPattern;
   String get listTag => 'ol';
 
   const OrderedListSyntax();
@@ -516,7 +518,7 @@
   bool canParse(BlockParser parser) => true;
 
   Node parse(BlockParser parser) {
-    final childLines = [];
+    var childLines = <String>[];
 
     // Eat until we hit something that ends a paragraph.
     while (!BlockSyntax.isAtBlockEnd(parser)) {
@@ -524,7 +526,7 @@
       parser.advance();
     }
 
-    final contents = parser.document.parseInline(childLines.join('\n'));
+    var contents = parser.document.parseInline(childLines.join('\n'));
     return new Element('p', contents);
   }
 }
diff --git a/pkgs/markdown/lib/src/document.dart b/pkgs/markdown/lib/src/document.dart
index 79f7c31..71a29d9 100644
--- a/pkgs/markdown/lib/src/document.dart
+++ b/pkgs/markdown/lib/src/document.dart
@@ -1,4 +1,4 @@
-library markdown.document;
+library markdown.src.document;
 
 import 'ast.dart';
 import 'block_parser.dart';
@@ -19,16 +19,16 @@
     // [id]: http:foo.com "some title"
     // Where there may whitespace in there, and where the title may be in
     // single quotes, double quotes, or parentheses.
-    final indent = r'^[ ]{0,3}'; // Leading indentation.
-    final id = r'\[([^\]]+)\]'; // Reference id in [brackets].
-    final quote = r'"[^"]+"'; // Title in "double quotes".
-    final apos = r"'[^']+'"; // Title in 'single quotes'.
-    final paren = r"\([^)]+\)"; // Title in (parentheses).
-    final pattern = new RegExp(
-        '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
+    var indent = r'^[ ]{0,3}'; // Leading indentation.
+    var id = r'\[([^\]]+)\]'; // Reference id in [brackets].
+    var quote = r'"[^"]+"'; // Title in "double quotes".
+    var apos = r"'[^']+'"; // Title in 'single quotes'.
+    var paren = r"\([^)]+\)"; // Title in (parentheses).
+    var pattern =
+        new RegExp('$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
 
-    for (int i = 0; i < lines.length; i++) {
-      final match = pattern.firstMatch(lines[i]);
+    for (var i = 0; i < lines.length; i++) {
+      var match = pattern.firstMatch(lines[i]);
       if (match != null) {
         // Parse the link.
         var id = match[1];
@@ -57,13 +57,13 @@
 
   /// Parse the given [lines] of markdown to a series of AST nodes.
   List<Node> parseLines(List<String> lines) {
-    final parser = new BlockParser(lines, this);
+    var parser = new BlockParser(lines, this);
 
-    final blocks = [];
+    var blocks = <Node>[];
     while (!parser.isDone) {
-      for (final syntax in BlockSyntax.syntaxes) {
+      for (var syntax in BlockSyntax.syntaxes) {
         if (syntax.canParse(parser)) {
-          final block = syntax.parse(parser);
+          var block = syntax.parse(parser);
           if (block != null) blocks.add(block);
           break;
         }
diff --git a/pkgs/markdown/lib/src/html_renderer.dart b/pkgs/markdown/lib/src/html_renderer.dart
index 835c178..edb09a0 100644
--- a/pkgs/markdown/lib/src/html_renderer.dart
+++ b/pkgs/markdown/lib/src/html_renderer.dart
@@ -2,38 +2,37 @@
 // 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.
 
-library markdown.html_renderer;
+library markdown.src.html_renderer;
 
 import 'ast.dart';
 import 'document.dart';
 import 'inline_parser.dart';
 
 /// Converts the given string of markdown to HTML.
-String markdownToHtml(String markdown, {List<InlineSyntax> inlineSyntaxes,
-    Resolver linkResolver, Resolver imageLinkResolver,
+String markdownToHtml(String markdown,
+    {List<InlineSyntax> inlineSyntaxes,
+    Resolver linkResolver,
+    Resolver imageLinkResolver,
     bool inlineOnly: false}) {
   var document = new Document(
       inlineSyntaxes: inlineSyntaxes,
       imageLinkResolver: imageLinkResolver,
       linkResolver: linkResolver);
 
-  if (inlineOnly) {
-    return renderToHtml(document.parseInline(markdown));
-  } else {
-    // Replace windows line endings with unix line endings, and split.
-    var lines = markdown.replaceAll('\r\n', '\n').split('\n');
-    document.parseRefLinks(lines);
-    var blocks = document.parseLines(lines);
-    return renderToHtml(blocks);
-  }
+  if (inlineOnly) return renderToHtml(document.parseInline(markdown));
+
+  // Replace windows line endings with unix line endings, and split.
+  var lines = markdown.replaceAll('\r\n', '\n').split('\n');
+  document.parseRefLinks(lines);
+
+  return renderToHtml(document.parseLines(lines));
 }
 
 String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes);
 
 /// Translates a parsed AST to HTML.
 class HtmlRenderer implements NodeVisitor {
-  static final _BLOCK_TAGS = new RegExp(
-      'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
+  static final _blockTags = new RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
 
   StringBuffer buffer;
 
@@ -53,16 +52,17 @@
 
   bool visitElementBefore(Element element) {
     // Hackish. Separate block-level elements with newlines.
-    if (!buffer.isEmpty && _BLOCK_TAGS.firstMatch(element.tag) != null) {
+    if (!buffer.isEmpty && _blockTags.firstMatch(element.tag) != null) {
       buffer.write('\n');
     }
 
     buffer.write('<${element.tag}');
 
     // Sort the keys so that we generate stable output.
-    final attributeNames = element.attributes.keys.toList();
+    var attributeNames = element.attributes.keys.toList();
     attributeNames.sort((a, b) => a.compareTo(b));
-    for (final name in attributeNames) {
+
+    for (var name in attributeNames) {
       buffer.write(' $name="${element.attributes[name]}"');
     }
 
diff --git a/pkgs/markdown/lib/src/inline_parser.dart b/pkgs/markdown/lib/src/inline_parser.dart
index c9cf98a..5c11d7f 100644
--- a/pkgs/markdown/lib/src/inline_parser.dart
+++ b/pkgs/markdown/lib/src/inline_parser.dart
@@ -2,7 +2,7 @@
 // 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.
 
-library markdown.inline_parser;
+library markdown.src.inline_parser;
 
 import 'ast.dart';
 import 'document.dart';
@@ -26,7 +26,6 @@
     new TextSyntax(r'\s*[A-Za-z0-9]+'),
 
     // The real syntaxes.
-
     new AutolinkSyntax(),
     new LinkSyntax(),
     new ImageLinkSyntax(),
@@ -75,13 +74,15 @@
   final List<TagState> _stack;
 
   InlineParser(this.source, this.document) : _stack = <TagState>[] {
-    /// User specified syntaxes will be the first syntaxes to be evaluated.
+    // User specified syntaxes are the first syntaxes to be evaluated.
     if (document.inlineSyntaxes != null) {
       syntaxes.addAll(document.inlineSyntaxes);
     }
+
     syntaxes.addAll(_defaultSyntaxes);
+
     // Custom link resolvers goes after the generic text syntax.
-    syntaxes.insertAll(1, <InlineSyntax>[
+    syntaxes.insertAll(1, [
       new LinkSyntax(linkResolver: document.linkResolver),
       new ImageLinkSyntax(linkResolver: document.imageLinkResolver)
     ]);
@@ -92,25 +93,28 @@
     _stack.add(new TagState(0, 0, null));
 
     while (!isDone) {
-      bool matched = false;
+      var matched = false;
 
       // See if any of the current tags on the stack match. We don't allow tags
-      // of the same kind to nest, so this takes priority over other possible // matches.
-      for (int i = _stack.length - 1; i > 0; i--) {
+      // of the same kind to nest, so this takes priority over other possible
+      // matches.
+      for (var i = _stack.length - 1; i > 0; i--) {
         if (_stack[i].tryMatch(this)) {
           matched = true;
           break;
         }
       }
+
       if (matched) continue;
 
       // See if the current text matches any defined markdown syntax.
-      for (final syntax in syntaxes) {
+      for (var syntax in syntaxes) {
         if (syntax.tryMatch(this)) {
           matched = true;
           break;
         }
       }
+
       if (matched) continue;
 
       // If we got here, it's just text.
@@ -127,17 +131,16 @@
   }
 
   void writeTextRange(int start, int end) {
-    if (end > start) {
-      final text = source.substring(start, end);
-      final nodes = _stack.last.children;
+    if (end <= start) return;
 
-      // If the previous node is text too, just append.
-      if ((nodes.length > 0) && (nodes.last is Text)) {
-        final newNode = new Text('${nodes.last.text}$text');
-        nodes[nodes.length - 1] = newNode;
-      } else {
-        nodes.add(new Text(text));
-      }
+    var text = source.substring(start, end);
+    var nodes = _stack.last.children;
+
+    // If the previous node is text too, just append.
+    if (nodes.length > 0 && nodes.last is Text) {
+      nodes[nodes.length - 1] = new Text('${nodes.last.text}$text');
+    } else {
+      nodes.add(new Text(text));
     }
   }
 
@@ -147,6 +150,7 @@
 
   // TODO(rnystrom): Only need this because RegExp doesn't let you start
   // searching from a given offset.
+  @deprecated
   String get currentSource => source.substring(pos, source.length);
 
   bool get isDone => pos == source.length;
@@ -168,16 +172,15 @@
   InlineSyntax(String pattern) : pattern = new RegExp(pattern, multiLine: true);
 
   bool tryMatch(InlineParser parser) {
-    final startMatch = pattern.firstMatch(parser.currentSource);
-    if ((startMatch != null) && (startMatch.start == 0)) {
+    var startMatch = pattern.matchAsPrefix(parser.source, parser.pos);
+    if (startMatch != null) {
       // Write any existing plain text up to this point.
       parser.writeText();
 
-      if (onMatch(parser, startMatch)) {
-        parser.consume(startMatch[0].length);
-      }
+      if (onMatch(parser, startMatch)) parser.consume(startMatch[0].length);
       return true;
     }
+
     return false;
   }
 
@@ -187,6 +190,7 @@
 /// Matches stuff that should just be passed through as straight text.
 class TextSyntax extends InlineSyntax {
   final String substitute;
+
   TextSyntax(String pattern, {String sub})
       : super(pattern),
         substitute = sub;
@@ -210,10 +214,9 @@
   // TODO(rnystrom): Make case insensitive.
 
   bool onMatch(InlineParser parser, Match match) {
-    final url = match[1];
-
-    final anchor = new Element.text('a', escapeHtml(url))
-      ..attributes['href'] = url;
+    var url = match[1];
+    var anchor = new Element.text('a', escapeHtml(url));
+    anchor.attributes['href'] = url;
     parser.addNode(anchor);
 
     return true;
@@ -226,15 +229,13 @@
   final RegExp endPattern;
   final String tag;
 
-  TagSyntax(String pattern, {String tag, String end})
+  TagSyntax(String pattern, {this.tag, String end})
       : super(pattern),
-        endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
-        tag = tag;
-  // TODO(rnystrom): Doing this.field doesn't seem to work with named args.
+        endPattern = new RegExp((end != null) ? end : pattern, multiLine: true);
 
   bool onMatch(InlineParser parser, Match match) {
-    parser._stack.add(
-        new TagState(parser.pos, parser.pos + match[0].length, this));
+    parser._stack
+        .add(new TagState(parser.pos, parser.pos + match[0].length, this));
     return true;
   }
 
@@ -255,9 +256,9 @@
   /// inline styles as well as optional titles for inline links. To make that
   /// a bit more palatable, this breaks it into pieces.
   static get linkPattern {
-    final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
-    final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
-    final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
+    var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
+    var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
+    var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
     return '\](?:($refLink|$inlineLink)|)';
 
     // The groups matched by this are:
@@ -277,7 +278,7 @@
     // link at all. Instead, we allow users of the library to specify a special
     // resolver function ([linkResolver]) that may choose to handle
     // this. Otherwise, it's just treated as plain text.
-    if (isNullOrEmpty(match[1])) {
+    if (match[1] == null) {
       if (linkResolver == null) return null;
 
       // Treat the contents as unparsed text even if they happen to match. This
@@ -289,20 +290,20 @@
       resolved = true;
       return linkResolver(textToResolve);
     } else {
-      Link link = getLink(parser, match, state);
+      var link = getLink(parser, match, state);
       if (link == null) return null;
 
-      final Element node = new Element('a', state.children)
-        ..attributes["href"] = escapeHtml(link.url)
-        ..attributes['title'] = escapeHtml(link.title);
+      var node = new Element('a', state.children);
 
-      cleanMap(node.attributes);
+      node.attributes["href"] = escapeHtml(link.url);
+      if (link.title != null) node.attributes['title'] = escapeHtml(link.title);
+
       return node;
     }
   }
 
   Link getLink(InlineParser parser, Match match, TagState state) {
-    if ((match[3] != null) && (match[3] != '')) {
+    if (match[3] != null && match[3] != '') {
       // Inline link like [foo](url).
       var url = match[3];
       var title = match[4];
@@ -316,10 +317,12 @@
     } else {
       var id;
       // Reference link like [foo] [bar].
-      if (match[2] == '')
-          // The id is empty ("[]") so infer it from the contents.
-          id = parser.source.substring(state.startPos + 1, parser.pos);
-      else id = match[2];
+      if (match[2] == '') {
+        // The id is empty ("[]") so infer it from the contents.
+        id = parser.source.substring(state.startPos + 1, parser.pos);
+      } else {
+        id = match[2];
+      }
 
       // References are case-insensitive.
       id = id.toLowerCase();
@@ -328,8 +331,9 @@
   }
 
   bool onMatchEnd(InlineParser parser, Match match, TagState state) {
-    Node node = createNode(parser, match, state);
+    var node = createNode(parser, match, state);
     if (node == null) return false;
+
     parser.addNode(node);
     return true;
   }
@@ -339,21 +343,24 @@
 /// `![alternate text][url reference]`.
 class ImageLinkSyntax extends LinkSyntax {
   final Resolver linkResolver;
+
   ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\[');
 
   Node createNode(InlineParser parser, Match match, TagState state) {
     var node = super.createNode(parser, match, state);
+
     if (resolved) return node;
     if (node == null) return null;
 
-    final Element imageElement = new Element.withTag("img")
-      ..attributes["src"] = node.attributes["href"]
-      ..attributes["title"] = node.attributes["title"]
-      ..attributes["alt"] = node.children
-      .map((e) => isNullOrEmpty(e) || e is! Text ? '' : e.text)
-      .join(' ');
+    var imageElement = new Element.withTag("img");
+    imageElement.attributes["src"] = node.attributes["href"];
 
-    cleanMap(imageElement.attributes);
+    if (node.attributes.containsKey("title")) {
+      imageElement.attributes["title"] = node.attributes["title"];
+    }
+
+    var alt = node.children.map((e) => e is! Text ? '' : e.text).join(" ");
+    if (alt != "") imageElement.attributes["alt"] = alt;
 
     node.children
       ..clear()
@@ -393,8 +400,8 @@
   /// Attempts to close this tag by matching the current text against its end
   /// pattern.
   bool tryMatch(InlineParser parser) {
-    Match endMatch = syntax.endPattern.firstMatch(parser.currentSource);
-    if ((endMatch != null) && (endMatch.start == 0)) {
+    var endMatch = syntax.endPattern.matchAsPrefix(parser.source, parser.pos);
+    if (endMatch != null) {
       // Close the tag.
       close(parser, endMatch);
       return true;
@@ -411,14 +418,14 @@
     // means they are mismatched. Mismatched tags are treated as plain text in
     // markdown. So for each tag above this one, we write its start tag as text
     // and then adds its children to this one's children.
-    int index = parser._stack.indexOf(this);
+    var index = parser._stack.indexOf(this);
 
     // Remove the unmatched children.
-    final unmatchedTags = parser._stack.sublist(index + 1);
+    var unmatchedTags = parser._stack.sublist(index + 1);
     parser._stack.removeRange(index + 1, parser._stack.length);
 
     // Flatten them out onto this tag.
-    for (final unmatched in unmatchedTags) {
+    for (var unmatched in unmatchedTags) {
       // Write the start tag as text.
       parser.writeTextRange(unmatched.startPos, unmatched.endPos);
 
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart
index 4b5f6d5..e254d26 100644
--- a/pkgs/markdown/lib/src/util.dart
+++ b/pkgs/markdown/lib/src/util.dart
@@ -1,20 +1,9 @@
-library markdown.util;
+library markdown.src.util;
 
 /// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
 String escapeHtml(String html) {
-  if (html == '' || html == null) return null;
   return html
       .replaceAll('&', '&amp;')
       .replaceAll('<', '&lt;')
       .replaceAll('>', '&gt;');
 }
-
-/// Removes null or empty values from [map].
-void cleanMap(Map map) {
-  map.keys.where((e) => isNullOrEmpty(map[e])).toList().forEach(map.remove);
-}
-
-/// Returns true if an object is null or an empty string.
-bool isNullOrEmpty(object) {
-  return object == null || object == '';
-}
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
index 9a86724..3049abb 100644
--- a/pkgs/markdown/pubspec.yaml
+++ b/pkgs/markdown/pubspec.yaml
@@ -1,5 +1,5 @@
 name: markdown
-version: 0.7.2-dev
+version: 0.7.2
 author: Dart Team <misc@dartlang.org>
 description: A library for converting markdown to HTML.
 homepage: https://github.com/dart-lang/markdown
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index e2d1b40..e0e8e11 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -9,16 +9,19 @@
 
 import 'package:markdown/markdown.dart';
 
-import 'utils.dart';
+import 'util.dart';
 
 /// Most of these tests are based on observing how showdown behaves:
 /// http://softwaremaniacs.org/playground/showdown-highlight/
 void main() {
   group('Paragraphs', () {
-    validate('consecutive lines form a single paragraph', '''
+    validate(
+        'consecutive lines form a single paragraph',
+        '''
         This is the first line.
         This is the second line.
-        ''', '''
+        ''',
+        '''
         <p>This is the first line.
         This is the second line.</p>
         ''');
@@ -28,163 +31,229 @@
     // code significantly cleaner, we should consider ourselves free to change
     // these tests.
 
-    validate('are terminated by a header', '''
+    validate(
+        'are terminated by a header',
+        '''
         para
         # header
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <h1>header</h1>
         ''');
 
-    validate('are terminated by a setext header', '''
+    validate(
+        'are terminated by a setext header',
+        '''
         para
         header
         ==
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <h1>header</h1>
         ''');
 
-    validate('are terminated by a hr', '''
+    validate(
+        'are terminated by a hr',
+        '''
         para
         ___
-        ''', '''
+        ''',
+        '''
         <p>para</p>
         <hr />
         ''');
 
-    validate('consume an unordered list', '''
+    validate(
+        'consume an unordered list',
+        '''
         para
         * list
-        ''', '''
+        ''',
+        '''
         <p>para
         * list</p>
         ''');
 
-    validate('consume an ordered list', '''
+    validate(
+        'consume an ordered list',
+        '''
         para
         1. list
-        ''', '''
+        ''',
+        '''
         <p>para
         1. list</p>
         ''');
 
     // Windows line endings have a \r\n format
     // instead of the unix \n format.
-    validate('take account of windows line endings', '''
+    validate(
+        'take account of windows line endings',
+        '''
         line1\r\n\r\n        line2\r\n
-        ''', '''
+        ''',
+        '''
         <p>line1</p>
         <p>line2</p>
         ''');
   });
 
   group('Setext headers', () {
-    validate('h1', '''
+    validate(
+        'h1',
+        '''
         text
         ===
-        ''', '''
+        ''',
+        '''
         <h1>text</h1>
         ''');
 
-    validate('h2', '''
+    validate(
+        'h2',
+        '''
         text
         ---
-        ''', '''
+        ''',
+        '''
         <h2>text</h2>
         ''');
 
-    validate('h1 on first line becomes text', '''
+    validate(
+        'h1 on first line becomes text',
+        '''
         ===
-        ''', '''
+        ''',
+        '''
         <p>===</p>
         ''');
 
-    validate('h2 on first line becomes text', '''
+    validate(
+        'h2 on first line becomes text',
+        '''
         -
-        ''', '''
+        ''',
+        '''
         <p>-</p>
         ''');
 
-    validate('h1 turns preceding list into text', '''
+    validate(
+        'h1 turns preceding list into text',
+        '''
         - list
         ===
-        ''', '''
+        ''',
+        '''
         <h1>- list</h1>
         ''');
 
-    validate('h2 turns preceding list into text', '''
+    validate(
+        'h2 turns preceding list into text',
+        '''
         - list
         ===
-        ''', '''
+        ''',
+        '''
         <h1>- list</h1>
         ''');
 
-    validate('h1 turns preceding blockquote into text', '''
+    validate(
+        'h1 turns preceding blockquote into text',
+        '''
         > quote
         ===
-        ''', '''
+        ''',
+        '''
         <h1>> quote</h1>
         ''');
 
-    validate('h2 turns preceding blockquote into text', '''
+    validate(
+        'h2 turns preceding blockquote into text',
+        '''
         > quote
         ===
-        ''', '''
+        ''',
+        '''
         <h1>> quote</h1>
         ''');
   });
 
   group('Headers', () {
-    validate('h1', '''
+    validate(
+        'h1',
+        '''
         # header
-        ''', '''
+        ''',
+        '''
         <h1>header</h1>
         ''');
 
-    validate('h2', '''
+    validate(
+        'h2',
+        '''
         ## header
-        ''', '''
+        ''',
+        '''
         <h2>header</h2>
         ''');
 
-    validate('h3', '''
+    validate(
+        'h3',
+        '''
         ### header
-        ''', '''
+        ''',
+        '''
         <h3>header</h3>
         ''');
 
-    validate('h4', '''
+    validate(
+        'h4',
+        '''
         #### header
-        ''', '''
+        ''',
+        '''
         <h4>header</h4>
         ''');
 
-    validate('h5', '''
+    validate(
+        'h5',
+        '''
         ##### header
-        ''', '''
+        ''',
+        '''
         <h5>header</h5>
         ''');
 
-    validate('h6', '''
+    validate(
+        'h6',
+        '''
         ###### header
-        ''', '''
+        ''',
+        '''
         <h6>header</h6>
         ''');
 
-    validate('trailing "#" are removed', '''
+    validate(
+        'trailing "#" are removed',
+        '''
         # header ######
-        ''', '''
+        ''',
+        '''
         <h1>header</h1>
         ''');
   });
 
   group('Unordered lists', () {
-    validate('asterisk, plus and hyphen', '''
+    validate(
+        'asterisk, plus and hyphen',
+        '''
         * star
         - dash
         + plus
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>star</li>
           <li>dash</li>
@@ -192,22 +261,28 @@
         </ul>
         ''');
 
-    validate('allow numbered lines after first', '''
+    validate(
+        'allow numbered lines after first',
+        '''
         * a
         1. b
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>a</li>
           <li>b</li>
         </ul>
         ''');
 
-    validate('allow a tab after the marker', '''
+    validate(
+        'allow a tab after the marker',
+        '''
         *\ta
         +\tb
         -\tc
         1.\td
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>a</li>
           <li>b</li>
@@ -216,23 +291,29 @@
         </ul>
         ''');
 
-    validate('wrap items in paragraphs if blank lines separate', '''
+    validate(
+        'wrap items in paragraphs if blank lines separate',
+        '''
         * one
 
         * two
-        ''', '''
+        ''',
+        '''
         <ul>
           <li><p>one</p></li>
           <li><p>two</p></li>
         </ul>
         ''');
 
-    validate('force paragraph on item before and after blank lines', '''
+    validate(
+        'force paragraph on item before and after blank lines',
+        '''
         *   one
         *   two
 
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>one</li>
           <li>
@@ -244,24 +325,30 @@
         </ul>
         ''');
 
-    validate('do not force paragraph if item is already block', '''
+    validate(
+        'do not force paragraph if item is already block',
+        '''
         * > quote
 
         * # header
-        ''', '''
+        ''',
+        '''
         <ul>
           <li><blockquote><p>quote</p></blockquote></li>
           <li><h1>header</h1></li>
         </ul>
         ''');
 
-    validate('can contain multiple paragraphs', '''
+    validate(
+        'can contain multiple paragraphs',
+        '''
         *   one
 
             two
 
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>
             <p>one</p>
@@ -273,11 +360,14 @@
         </ul>
         ''');
 
-    validate('can span newlines', '''
+    validate(
+        'can span newlines',
+        '''
         *   one
             two
         *   three
-        ''', '''
+        ''',
+        '''
         <ul>
           <li>
             <p>one
@@ -306,11 +396,14 @@
   });
 
   group('Ordered lists', () {
-    validate('start with numbers', '''
+    validate(
+        'start with numbers',
+        '''
         1. one
         45.  two
            12345. three
-        ''', '''
+        ''',
+        '''
         <ol>
           <li>one</li>
           <li>two</li>
@@ -318,10 +411,13 @@
         </ol>
         ''');
 
-    validate('allow unordered lines after first', '''
+    validate(
+        'allow unordered lines after first',
+        '''
         1. a
         * b
-        ''', '''
+        ''',
+        '''
         <ol>
           <li>a</li>
           <li>b</li>
@@ -330,30 +426,39 @@
   });
 
   group('Blockquotes', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
         > blah
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>blah</p>
         </blockquote>
         ''');
 
-    validate('with two paragraphs', '''
+    validate(
+        'with two paragraphs',
+        '''
         > first
         >
         > second
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>first</p>
           <p>second</p>
         </blockquote>
         ''');
 
-    validate('nested', '''
+    validate(
+        'nested',
+        '''
         > one
         >> two
         > > > three
-        ''', '''
+        ''',
+        '''
         <blockquote>
           <p>one</p>
           <blockquote>
@@ -367,32 +472,41 @@
   });
 
   group('Code blocks', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
             code
-        ''', '''
+        ''',
+        '''
         <pre><code>code</code></pre>
         ''');
 
-    validate('include leading whitespace after indentation', '''
+    validate(
+        'include leading whitespace after indentation',
+        '''
             zero
              one
               two
                three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one
           two
            three</code></pre>
         ''');
 
-    validate('code blocks separated by newlines form one block', '''
+    validate(
+        'code blocks separated by newlines form one block',
+        '''
             zero
             one
 
             two
 
             three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one
 
@@ -401,7 +515,9 @@
          three</code></pre>
         ''');
 
-    validate('code blocks separated by two newlines form multiple blocks', '''
+    validate(
+        'code blocks separated by two newlines form multiple blocks',
+        '''
             zero
             one
 
@@ -410,73 +526,95 @@
 
 
             three
-        ''', '''
+        ''',
+        '''
         <pre><code>zero
          one</code></pre>
         <pre><code>two</code></pre>
         <pre><code>three</code></pre>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
             <&>
-        ''', '''
+        ''',
+        '''
         <pre><code>&lt;&amp;&gt;</code></pre>
         ''');
   });
 
   group('Fenced code blocks', () {
-    validate('without an optional language identifier', '''
+    validate(
+        'without an optional language identifier',
+        '''
         ```
         code
         ```
-        ''', '''
+        ''',
+        '''
         <pre><code>code
         </code></pre>
         ''');
 
-    validate('with an optional language identifier', '''
+    validate(
+        'with an optional language identifier',
+        '''
         ```dart
         code
         ```
-        ''', '''
+        ''',
+        '''
         <pre class="dart"><code>code
         </code></pre>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
         ```
         <&>
         ```
-        ''', '''
+        ''',
+        '''
         <pre><code>&lt;&amp;&gt;
         </code></pre>
         ''');
 
-    validate('Pandoc style without language identifier', '''
+    validate(
+        'Pandoc style without language identifier',
+        '''
         ~~~~~
         code
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre><code>code
         </code></pre>
         ''');
 
-    validate('Pandoc style with language identifier', '''
+    validate(
+        'Pandoc style with language identifier',
+        '''
         ~~~~~dart
         code
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre class="dart"><code>code
         </code></pre>
         ''');
 
-    validate('Pandoc style with inner tildes row', '''
+    validate(
+        'Pandoc style with inner tildes row',
+        '''
         ~~~~~
         ~~~
         code
         ~~~
         ~~~~~
-        ''', '''
+        ''',
+        '''
         <pre><code>~~~
         code
         ~~~
@@ -485,68 +623,92 @@
   });
 
   group('Horizontal rules', () {
-    validate('from dashes', '''
+    validate(
+        'from dashes',
+        '''
         ---
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('from asterisks', '''
+    validate(
+        'from asterisks',
+        '''
         ***
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('from underscores', '''
+    validate(
+        'from underscores',
+        '''
         ___
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
 
-    validate('can include up to two spaces', '''
+    validate(
+        'can include up to two spaces',
+        '''
         _ _  _
-        ''', '''
+        ''',
+        '''
         <hr />
         ''');
   });
 
   group('Block-level HTML', () {
-    validate('single line', '''
+    validate(
+        'single line',
+        '''
         <table></table>
-        ''', '''
+        ''',
+        '''
         <table></table>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         <table>
             blah
         </table>
-        ''', '''
+        ''',
+        '''
         <table>
             blah
         </table>
         ''');
 
-    validate('blank line ends block', '''
+    validate(
+        'blank line ends block',
+        '''
         <table>
             blah
         </table>
 
         para
-        ''', '''
+        ''',
+        '''
         <table>
             blah
         </table>
         <p>para</p>
         ''');
 
-    validate('HTML can be bogus', '''
+    validate(
+        'HTML can be bogus',
+        '''
         <bogus>
         blah
         </weird>
 
         para
-        ''', '''
+        ''',
+        '''
         <bogus>
         blah
         </weird>
@@ -555,319 +717,466 @@
   });
 
   group('Strong', () {
-    validate('using asterisks', '''
+    validate(
+        'using asterisks',
+        '''
         before **strong** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('using underscores', '''
+    validate(
+        'using underscores',
+        '''
         before __strong__ after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('unmatched asterisks', '''
+    validate(
+        'unmatched asterisks',
+        '''
         before ** after
-        ''', '''
+        ''',
+        '''
         <p>before ** after</p>
         ''');
 
-    validate('unmatched underscores', '''
+    validate(
+        'unmatched underscores',
+        '''
         before __ after
-        ''', '''
+        ''',
+        '''
         <p>before __ after</p>
         ''');
 
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a **one** b __two__ c
-        ''', '''
+        ''',
+        '''
         <p>a <strong>one</strong> b <strong>two</strong> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before **first
         second** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>first
         second</strong> after</p>
         ''');
   });
 
   group('Emphasis and strong', () {
-    validate('single asterisks', '''
+    validate(
+        'single asterisks',
+        '''
         before *em* after
-        ''', '''
+        ''',
+        '''
         <p>before <em>em</em> after</p>
         ''');
 
-    validate('single underscores', '''
+    validate(
+        'single underscores',
+        '''
         before _em_ after
-        ''', '''
+        ''',
+        '''
         <p>before <em>em</em> after</p>
         ''');
 
-    validate('double asterisks', '''
+    validate(
+        'double asterisks',
+        '''
         before **strong** after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('double underscores', '''
+    validate(
+        'double underscores',
+        '''
         before __strong__ after
-        ''', '''
+        ''',
+        '''
         <p>before <strong>strong</strong> after</p>
         ''');
 
-    validate('unmatched asterisk', '''
+    validate(
+        'unmatched asterisk',
+        '''
         before *after
-        ''', '''
+        ''',
+        '''
         <p>before *after</p>
         ''');
 
-    validate('unmatched underscore', '''
+    validate(
+        'unmatched underscore',
+        '''
         before _after
-        ''', '''
+        ''',
+        '''
         <p>before _after</p>
         ''');
 
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a *one* b _two_ c
-        ''', '''
+        ''',
+        '''
         <p>a <em>one</em> b <em>two</em> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before *first
         second* after
-        ''', '''
+        ''',
+        '''
         <p>before <em>first
         second</em> after</p>
         ''');
 
-    validate('not processed when surrounded by spaces', '''
+    validate(
+        'not processed when surrounded by spaces',
+        '''
         a * b * c _ d _ e
-        ''', '''
+        ''',
+        '''
         <p>a * b * c _ d _ e</p>
         ''');
 
-    validate('strong then emphasis', '''
+    validate(
+        'strong then emphasis',
+        '''
         **strong***em*
-        ''', '''
+        ''',
+        '''
         <p><strong>strong</strong><em>em</em></p>
         ''');
 
-    validate('emphasis then strong', '''
+    validate(
+        'emphasis then strong',
+        '''
         *em***strong**
-        ''', '''
+        ''',
+        '''
         <p><em>em</em><strong>strong</strong></p>
         ''');
 
-    validate('emphasis inside strong', '''
+    validate(
+        'emphasis inside strong',
+        '''
         **strong *em***
-        ''', '''
+        ''',
+        '''
         <p><strong>strong <em>em</em></strong></p>
         ''');
 
-    validate('mismatched in nested', '''
+    validate(
+        'mismatched in nested',
+        '''
         *a _b* c_
-        ''', '''
+        ''',
+        '''
         <p><em>a _b</em> c_</p>
         ''');
 
-    validate('cannot nest tags of same type', '''
+    validate(
+        'cannot nest tags of same type',
+        '''
         *a _b *c* d_ e*
-        ''', '''
+        ''',
+        '''
         <p><em>a _b </em>c<em> d_ e</em></p>
         ''');
   });
 
   group('Inline code', () {
-    validate('simple case', '''
+    validate(
+        'simple case',
+        '''
         before `source` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>source</code> after</p>
         ''');
 
-    validate('unmatched backtick', '''
+    validate(
+        'unmatched backtick',
+        '''
         before ` after
-        ''', '''
+        ''',
+        '''
         <p>before ` after</p>
         ''');
-    validate('multiple spans in one text', '''
+    validate(
+        'multiple spans in one text',
+        '''
         a `one` b `two` c
-        ''', '''
+        ''',
+        '''
         <p>a <code>one</code> b <code>two</code> c</p>
         ''');
 
-    validate('multi-line', '''
+    validate(
+        'multi-line',
+        '''
         before `first
         second` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>first
         second</code> after</p>
         ''');
 
-    validate('simple double backticks', '''
+    validate(
+        'simple double backticks',
+        '''
         before ``source`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>source</code> after</p>
         ''');
 
-    validate('double backticks', '''
+    validate(
+        'double backticks',
+        '''
         before ``can `contain` backticks`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>can `contain` backticks</code> after</p>
         ''');
 
-    validate('double backticks with spaces', '''
+    validate(
+        'double backticks with spaces',
+        '''
         before `` `tick` `` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>`tick`</code> after</p>
         ''');
 
-    validate('multiline double backticks with spaces', '''
+    validate(
+        'multiline double backticks with spaces',
+        '''
         before ``in `tick`
         another`` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>in `tick`
         another</code> after</p>
         ''');
 
-    validate('ignore markup inside code', '''
+    validate(
+        'ignore markup inside code',
+        '''
         before `*b* _c_` after
-        ''', '''
+        ''',
+        '''
         <p>before <code>*b* _c_</code> after</p>
         ''');
 
-    validate('escape HTML characters', '''
+    validate(
+        'escape HTML characters',
+        '''
         `<&>`
-        ''', '''
+        ''',
+        '''
         <p><code>&lt;&amp;&gt;</code></p>
         ''');
 
-    validate('escape HTML tags', '''
+    validate(
+        'escape HTML tags',
+        '''
         '*' `<em>`
-        ''', '''
+        ''',
+        '''
         <p>'*' <code>&lt;em&gt;</code></p>
         ''');
   });
 
   group('HTML encoding', () {
-    validate('less than and ampersand are escaped', '''
+    validate(
+        'less than and ampersand are escaped',
+        '''
         < &
-        ''', '''
+        ''',
+        '''
         <p>&lt; &amp;</p>
         ''');
-    validate('greater than is not escaped', '''
+    validate(
+        'greater than is not escaped',
+        '''
         not you >
-        ''', '''
+        ''',
+        '''
         <p>not you ></p>
         ''');
-    validate('existing entities are untouched', '''
+    validate(
+        'existing entities are untouched',
+        '''
         &amp;
-        ''', '''
+        ''',
+        '''
         <p>&amp;</p>
         ''');
   });
 
   group('Autolinks', () {
-    validate('basic link', '''
+    validate(
+        'basic link',
+        '''
         before <http://foo.com/> after
-        ''', '''
+        ''',
+        '''
         <p>before <a href="http://foo.com/">http://foo.com/</a> after</p>
         ''');
-    validate('handles ampersand in url', '''
+    validate(
+        'handles ampersand in url',
+        '''
         <http://foo.com/?a=1&b=2>
-        ''', '''
+        ''',
+        '''
         <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&amp;b=2</a></p>
         ''');
   });
 
   group('Reference links', () {
-    validate('double quotes for title', '''
+    validate(
+        'double quotes for title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com "woo"
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('single quoted title', """
+    validate(
+        'single quoted title',
+        """
         links [are] [a] awesome
 
         [a]: http://foo.com 'woo'
-        """, '''
+        """,
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('parentheses for title', '''
+    validate(
+        'parentheses for title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com (woo)
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('no title', '''
+    validate(
+        'no title',
+        '''
         links [are] [a] awesome
 
         [a]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('unknown link becomes plaintext', '''
+    validate(
+        'unknown link becomes plaintext',
+        '''
         [not] [known]
-        ''', '''
+        ''',
+        '''
         <p>[not] [known]</p>
         ''');
-    validate('can style link contents', '''
+    validate(
+        'can style link contents',
+        '''
         links [*are*] [a] awesome
 
         [a]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
         ''');
-    validate('inline styles after a bad link are processed', '''
+    validate(
+        'inline styles after a bad link are processed',
+        '''
         [bad] `code`
-        ''', '''
+        ''',
+        '''
         <p>[bad] <code>code</code></p>
         ''');
-    validate('empty reference uses text from link', '''
+    validate(
+        'empty reference uses text from link',
+        '''
         links [are][] awesome
 
         [are]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('references are case-insensitive', '''
+    validate(
+        'references are case-insensitive',
+        '''
         links [ARE][] awesome
 
         [are]: http://foo.com
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">ARE</a> awesome</p>
         ''');
   });
 
   group('Inline links', () {
-    validate('double quotes for title', '''
+    validate(
+        'double quotes for title',
+        '''
         links [are](http://foo.com "woo") awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
         ''');
-    validate('no title', '''
+    validate(
+        'no title',
+        '''
         links [are] (http://foo.com) awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com">are</a> awesome</p>
         ''');
-    validate('can style link contents', '''
+    validate(
+        'can style link contents',
+        '''
         links [*are*](http://foo.com) awesome
-        ''', '''
+        ''',
+        '''
         <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
         ''');
   });
 
   group('Inline Images', () {
-    validate('image', '''
+    validate(
+        'image',
+        '''
         ![](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -875,9 +1184,12 @@
         </p>
         ''');
 
-    validate('alternate text', '''
+    validate(
+        'alternate text',
+        '''
         ![alternate text](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img alt="alternate text" src="http://foo.com/foo.png"></img>
@@ -885,18 +1197,24 @@
         </p>
         ''');
 
-    validate('title', '''
+    validate(
+        'title',
+        '''
         ![](http://foo.com/foo.png "optional title")
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
           </a>
         </p>
         ''');
-    validate('invalid alt text', '''
+    validate(
+        'invalid alt text',
+        '''
         ![`alt`](http://foo.com/foo.png)
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -906,10 +1224,13 @@
   });
 
   group('Reference Images', () {
-    validate('image', '''
+    validate(
+        'image',
+        '''
         ![][foo]
         [foo]: http://foo.com/foo.png
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img src="http://foo.com/foo.png"></img>
@@ -917,10 +1238,13 @@
         </p>
         ''');
 
-    validate('alternate text', '''
+    validate(
+        'alternate text',
+        '''
         ![alternate text][foo]
         [foo]: http://foo.com/foo.png
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png">
             <img alt="alternate text" src="http://foo.com/foo.png"></img>
@@ -928,10 +1252,13 @@
         </p>
         ''');
 
-    validate('title', '''
+    validate(
+        'title',
+        '''
         ![][foo]
         [foo]: http://foo.com/foo.png "optional title"
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
@@ -939,10 +1266,13 @@
         </p>
         ''');
 
-    validate('invalid alt text', '''
+    validate(
+        'invalid alt text',
+        '''
         ![`alt`][foo]
         [foo]: http://foo.com/foo.png "optional title"
-        ''', '''
+        ''',
+        '''
         <p>
           <a href="http://foo.com/foo.png" title="optional title">
             <img src="http://foo.com/foo.png" title="optional title"></img>
@@ -954,37 +1284,53 @@
   group('Resolver', () {
     nyanResolver(text) => new Text('~=[,,_${text}_,,]:3');
 
-    validate('simple link resolver', '''
+    validate(
+        'simple link resolver',
+        '''
         resolve [this] thing
-        ''', '''
+        ''',
+        '''
         <p>resolve ~=[,,_this_,,]:3 thing</p>
-        ''', linkResolver: nyanResolver);
-    validate('simple image resolver', '''
+        ''',
+        linkResolver: nyanResolver);
+    validate(
+        'simple image resolver',
+        '''
         resolve ![this] thing
-        ''', '''
+        ''',
+        '''
         <p>resolve ~=[,,_this_,,]:3 thing</p>
-        ''', imageLinkResolver: nyanResolver);
+        ''',
+        imageLinkResolver: nyanResolver);
 
-    validate('can resolve link containing inline tags', '''
+    validate(
+        'can resolve link containing inline tags',
+        '''
         resolve [*star* _underline_] thing
-        ''', '''
+        ''',
+        '''
         <p>resolve ~=[,,_*star* _underline__,,]:3 thing</p>
-        ''', linkResolver: nyanResolver);
+        ''',
+        linkResolver: nyanResolver);
   });
 
   group('Custom inline syntax', () {
     var nyanSyntax = [new TextSyntax('nyan', sub: '~=[,,_,,]:3')];
 
-    validate('simple inline syntax', '''
+    validate(
+        'simple inline syntax',
+        '''
         nyan
-        ''', '''
+        ''',
+        '''
         <p>~=[,,_,,]:3</p>
-        ''', inlineSyntaxes: nyanSyntax);
+        ''',
+        inlineSyntaxes: nyanSyntax);
 
     validate('dart custom links', 'links [are<foo>] awesome',
         '<p>links <a>are&lt;foo></a> awesome</p>',
-        linkResolver: (text) => new Element.text(
-            'a', text.replaceAll('<', '&lt;')));
+        linkResolver: (text) =>
+            new Element.text('a', text.replaceAll('<', '&lt;')));
 
     // TODO(amouravski): need more tests here for custom syntaxes, as some
     // things are not quite working properly. The regexps are sometime a little
@@ -992,42 +1338,70 @@
   });
 
   group('Inline only', () {
-    validate('simple line', '''
+    validate(
+        'simple line',
+        '''
         This would normally create a paragraph.
-        ''', '''
+        ''',
+        '''
         This would normally create a paragraph.
-        ''', inlineOnly: true);
-    validate('strong and em', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'strong and em',
+        '''
         This would _normally_ create a **paragraph**.
-        ''', '''
+        ''',
+        '''
         This would <em>normally</em> create a <strong>paragraph</strong>.
-        ''', inlineOnly: true);
-    validate('link', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'link',
+        '''
         This [link](http://www.example.com/) will work normally.
-        ''', '''
+        ''',
+        '''
         This <a href="http://www.example.com/">link</a> will work normally.
-        ''', inlineOnly: true);
-    validate('references do not work', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'references do not work',
+        '''
         [This][] shouldn't work, though.
-        ''', '''
+        ''',
+        '''
         [This][] shouldn't work, though.
-        ''', inlineOnly: true);
-    validate('less than and ampersand are escaped', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'less than and ampersand are escaped',
+        '''
         < &
-        ''', '''
+        ''',
+        '''
         &lt; &amp;
-        ''', inlineOnly: true);
-    validate('keeps newlines', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'keeps newlines',
+        '''
         This paragraph
         continues after a newline.
-        ''', '''
+        ''',
+        '''
         This paragraph
         continues after a newline.
-        ''', inlineOnly: true);
-    validate('ignores block-level markdown syntax', '''
+        ''',
+        inlineOnly: true);
+    validate(
+        'ignores block-level markdown syntax',
+        '''
         1. This will not be an <ol>.
-        ''', '''
+        ''',
+        '''
         1. This will not be an &lt;ol>.
-        ''', inlineOnly: true);
+        ''',
+        inlineOnly: true);
   });
 }
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart
new file mode 100644
index 0000000..fd99cb0
--- /dev/null
+++ b/pkgs/markdown/test/util.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2015, 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.
+
+library markdown.test.utils;
+
+import 'package:unittest/unittest.dart';
+
+import 'package:markdown/markdown.dart';
+
+/// Removes eight spaces of leading indentation from a multiline string.
+///
+/// Note that this is very sensitive to how the literals are styled. They should
+/// be:
+///     '''
+///     Text starts on own line. Lines up with subsequent lines.
+///     Lines are indented exactly 8 characters from the left margin.'''
+///
+/// This does nothing if text is only a single line.
+// TODO(nweiz): Make this auto-detect the indentation level from the first
+// non-whitespace line.
+String cleanUpLiteral(String text) {
+  var lines = text.split('\n');
+  if (lines.length <= 1) return text;
+
+  for (var j = 0; j < lines.length; j++) {
+    if (lines[j].length > 8) {
+      lines[j] = lines[j].substring(8, lines[j].length);
+    } else {
+      lines[j] = '';
+    }
+  }
+
+  return lines.join('\n');
+}
+
+void validate(String description, String markdown, String html,
+    {List<InlineSyntax> inlineSyntaxes,
+    Resolver linkResolver,
+    Resolver imageLinkResolver,
+    bool inlineOnly: false}) {
+  test(description, () {
+    markdown = cleanUpLiteral(markdown);
+    html = cleanUpLiteral(html);
+
+    var result = markdownToHtml(markdown,
+        inlineSyntaxes: inlineSyntaxes,
+        linkResolver: linkResolver,
+        imageLinkResolver: imageLinkResolver,
+        inlineOnly: inlineOnly);
+    var passed = compareOutput(html, result);
+
+    if (!passed) {
+      // Remove trailing newline.
+      html = html.substring(0, html.length - 1);
+
+      var sb = new StringBuffer();
+      sb.writeln('Expected: ${html.replaceAll("\n", "\n          ")}');
+      sb.writeln('  Actual: ${result.replaceAll("\n", "\n          ")}');
+
+      fail(sb.toString());
+    }
+  });
+}
+
+/// Does a loose comparison of the two strings of HTML. Ignores differences in
+/// newlines and indentation.
+bool compareOutput(String a, String b) {
+  int i = 0;
+  int j = 0;
+
+  skipIgnored(String s, int i) {
+    // Ignore newlines.
+    while ((i < s.length) && (s[i] == '\n')) {
+      i++;
+      // Ignore indentation.
+      while ((i < s.length) && (s[i] == ' ')) i++;
+    }
+
+    return i;
+  }
+
+  while (true) {
+    i = skipIgnored(a, i);
+    j = skipIgnored(b, j);
+
+    // If one string runs out of non-ignored strings, the other must too.
+    if (i == a.length) return j == b.length;
+    if (j == b.length) return i == a.length;
+
+    if (a[i] != b[j]) return false;
+    i++;
+    j++;
+  }
+}