Small modifications for null-safety (#296)
These include nullability hints for the migration tool, local variables for
non-promotable fields, and local variables for regex matches.
diff --git a/lib/src/block_parser.dart b/lib/src/block_parser.dart
index ca6102f..311c2be 100644
--- a/lib/src/block_parser.dart
+++ b/lib/src/block_parser.dart
@@ -51,6 +51,10 @@
/// A line of hyphens separated by at least one pipe.
final _tablePattern = RegExp(r'^[ ]{0,3}\|?( *:?\-+:? *\|)+( *:?\-+:? *)?$');
+/// A pattern which should never be used. It just satisfies non-nullability of
+/// pattern fields.
+final _dummyPattern = RegExp('');
+
/// Maintains the internal state needed to parse a series of lines into blocks
/// of Markdown suitable for further inline parsing.
class BlockParser {
@@ -162,7 +166,7 @@
const BlockSyntax();
/// Gets the regex used to identify the beginning of this block, if any.
- RegExp get pattern => null;
+ RegExp get pattern;
bool get canEndBlock => true;
@@ -219,6 +223,9 @@
/// Parses setext-style headers.
class SetextHeaderSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => _dummyPattern;
+
const SetextHeaderSyntax();
@override
@@ -263,7 +270,7 @@
var contents = UnparsedContent(lines.join('\n'));
- return Element(tag, [contents]);
+ return Element(tag /*!*/, [contents]);
}
bool _interperableAsParagraph(String line) =>
@@ -334,7 +341,7 @@
while (!parser.isDone) {
var match = pattern.firstMatch(parser.current);
if (match != null) {
- childLines.add(match[1]);
+ childLines.add(match[1] /*!*/);
parser.advance();
continue;
}
@@ -657,7 +664,7 @@
}
}
- Match match;
+ /*late*/ Match match;
bool tryMatch(RegExp pattern) {
match = pattern.firstMatch(parser.current);
return match != null;
@@ -689,12 +696,12 @@
// Horizontal rule takes precedence to a new list item.
break;
} else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
- var precedingWhitespace = match[1];
+ var precedingWhitespace = match[1] /*!*/;
var digits = match[2] ?? '';
if (startNumber == null && digits.isNotEmpty) {
startNumber = int.parse(digits);
}
- var marker = match[3];
+ var marker = match[3] /*!*/;
var firstWhitespace = match[5] ?? '';
var restWhitespace = match[6] ?? '';
var content = match[7] ?? '';
@@ -768,11 +775,14 @@
// We must post-process the list items, converting any top-level paragraph
// elements to just text elements.
for (var item in itemNodes) {
- for (var i = 0; i < item.children.length; i++) {
- var child = item.children[i];
- if (child is Element && child.tag == 'p') {
- item.children.removeAt(i);
- item.children.insertAll(i, child.children);
+ var children = item.children;
+ if (children != null) {
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ if (child is Element && child.tag == 'p') {
+ children.removeAt(i);
+ children.insertAll(i, child.children);
+ }
}
}
}
@@ -844,6 +854,9 @@
@override
bool get canEndBlock => false;
+ @override
+ RegExp get pattern => _dummyPattern;
+
const TableSyntax();
@override
@@ -874,9 +887,15 @@
var rows = <Element>[];
while (!parser.isDone && !BlockSyntax.isAtBlockEnd(parser)) {
var row = parseRow(parser, alignments, 'td');
- while (row.children.length < columnCount) {
- // Insert synthetic empty cells.
- row.children.add(Element.empty('td'));
+ var children = row.children;
+ if (children != null) {
+ while (children.length < columnCount) {
+ // Insert synthetic empty cells.
+ children.add(Element.empty('td'));
+ }
+ while (children.length > columnCount) {
+ children.removeLast();
+ }
}
while (row.children.length > columnCount) {
row.children.removeLast();
@@ -1036,6 +1055,9 @@
static final _whitespacePattern = RegExp(r'^\s*$');
@override
+ RegExp get pattern => _dummyPattern;
+
+ @override
bool get canEndBlock => false;
const ParagraphSyntax();
@@ -1169,7 +1191,7 @@
}
var label = match[1];
- var destination = match[2] ?? match[3];
+ var destination = (match[2] ?? match[3]) /*!*/;
var title = match[4];
// The label must contain at least one non-whitespace character.
diff --git a/lib/src/inline_parser.dart b/lib/src/inline_parser.dart
index 78d945d..d1d0eaa 100644
--- a/lib/src/inline_parser.dart
+++ b/lib/src/inline_parser.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:charcode/charcode.dart';
+import 'package:meta/meta.dart';
import 'ast.dart';
import 'document.dart';
@@ -113,7 +114,7 @@
}
// Unwind any unmatched tags and get the results.
- return _stack[0].close(this, null);
+ return _stack[0].close(this, null) ?? [];
}
int charAt(int index) => source.codeUnitAt(index);
@@ -303,7 +304,7 @@
@override
bool onMatch(InlineParser parser, Match match) {
- var url = match[1];
+ var url = match[1] /*!*/;
var text = parser.document.encodeHtml ? escapeHtml(url) : url;
var anchor = Element.text('a', text);
anchor.attributes['href'] = Uri.encodeFull('mailto:$url');
@@ -407,9 +408,10 @@
// https://github.github.com/gfm/#example-599
final trailingPunc = regExpTrailingPunc.firstMatch(url);
if (trailingPunc != null) {
- url = url.substring(0, url.length - trailingPunc[0].length);
- href = href.substring(0, href.length - trailingPunc[0].length);
- matchLength -= trailingPunc[0].length;
+ var trailingLength = trailingPunc[0].length;
+ url = url.substring(0, url.length - trailingLength);
+ href = href.substring(0, href.length - trailingLength);
+ matchLength -= trailingLength;
}
// If an autolink ends in a semicolon (;), we check to see if it appears
@@ -422,9 +424,10 @@
final entityRef = regExpEndsWithColon.firstMatch(url);
if (entityRef != null) {
// Strip out HTML entity reference
- url = url.substring(0, url.length - entityRef[0].length);
- href = href.substring(0, href.length - entityRef[0].length);
- matchLength -= entityRef[0].length;
+ var entityRefLength = entityRef[0].length;
+ url = url.substring(0, url.length - entityRefLength);
+ href = href.substring(0, href.length - entityRefLength);
+ matchLength -= entityRefLength;
}
}
@@ -499,12 +502,12 @@
final bool isFollowedByPunctuation;
_DelimiterRun._({
- this.char,
- this.length,
- this.isLeftFlanking,
- this.isRightFlanking,
- this.isPrecededByPunctuation,
- this.isFollowedByPunctuation,
+ @required this.char,
+ @required this.length,
+ @required this.isLeftFlanking,
+ @required this.isRightFlanking,
+ @required this.isPrecededByPunctuation,
+ @required this.isFollowedByPunctuation,
});
static _DelimiterRun tryParse(InlineParser parser, int runStart, int runEnd) {
diff --git a/tool/stats.dart b/tool/stats.dart
index 39e4aac..0edabca 100644
--- a/tool/stats.dart
+++ b/tool/stats.dart
@@ -51,10 +51,10 @@
}
var specifiedSection = options['section'] as String;
- var raw = options['raw'] as bool;
- var verbose = options['verbose'] as bool;
- var verboseLooseMatch = options['verbose-loose'] as bool;
- var updateFiles = options['update-files'] as bool;
+ var raw = options['raw'] as bool /*!*/;
+ var verbose = options['verbose'] as bool /*!*/;
+ var verboseLooseMatch = options['verbose-loose'] as bool /*!*/;
+ var updateFiles = options['update-files'] as bool /*!*/;
if (updateFiles && (raw || verbose || (specifiedSection != null))) {
stderr.writeln('The `update-files` flag must be used by itself');
diff --git a/tool/stats_lib.dart b/tool/stats_lib.dart
index 53fb0b9..11549df 100644
--- a/tool/stats_lib.dart
+++ b/tool/stats_lib.dart
@@ -74,10 +74,10 @@
factory CommonMarkTestCase.fromJson(Map<String, dynamic> json) {
return CommonMarkTestCase(
json['example'] as int,
- json['section'] as String,
+ json['section'] as String /*!*/,
json['start_line'] as int,
json['end_line'] as int,
- json['markdown'] as String,
+ json['markdown'] as String /*!*/,
json['html'] as String);
}