Null safe API (#347)
Migrate API to null safety.
This does not include some files in bin/, test/, and tool/:
* bin/markdown.dart requires package:args, which has not published a null safe release.
* tool/stats.dart requires package:args.
* tool/stats_lib.dart requires package:html, which has not published a null safe release.
* tool/dartdoc-compare.dart requires package:args.
* test/util.dart requires package:io, which has not published a null safe release.
* test/markdown_test.dart, test/vesion.dart import test/util.dart.
Most APIs upgraded to null safety very smoothly. Very clean change.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07693c7..e3c30cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
## 4.0.0-dev
+* Migrate package to Dart's null safety language feature, requiring Dart
+ 2.12 or higher.
* **Breaking change:** The TagSyntax constructor no longer takes an `end`
parameter. TagSyntax no longer implements `onMatchEnd`. Instead, TagSyntax
implements a method called `close` which creates and returns a Node, if a
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index e75787f..f3e934c 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -22,7 +22,7 @@
var start = DateTime.now();
// For a single benchmark, convert the source multiple times.
- String result;
+ late String result;
for (var j = 0; j < runsPerTrial; j++) {
result = markdownToHtml(source);
}
diff --git a/bin/markdown.dart b/bin/markdown.dart
index ce6fb82..5844354 100644
--- a/bin/markdown.dart
+++ b/bin/markdown.dart
@@ -1,3 +1,9 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.9
+
import 'dart:async';
import 'dart:io';
diff --git a/example/app.dart b/example/app.dart
index 9244692..b735ef8 100644
--- a/example/app.dart
+++ b/example/app.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
import 'dart:async';
import 'dart:html';
import 'package:markdown/markdown.dart' as md;
@@ -20,7 +24,7 @@
final basicRadio = querySelector('#basic-radio') as HtmlElement;
final commonmarkRadio = querySelector('#commonmark-radio') as HtmlElement;
final gfmRadio = querySelector('#gfm-radio') as HtmlElement;
-md.ExtensionSet extensionSet;
+md.ExtensionSet? extensionSet;
final extensionSets = {
'basic-radio': md.ExtensionSet.none,
@@ -46,7 +50,7 @@
// GitHub is the default extension set.
gfmRadio.attributes['checked'] = '';
- gfmRadio.querySelector('.glyph').text = 'radio_button_checked';
+ gfmRadio.querySelector('.glyph')!.text = 'radio_button_checked';
extensionSet = extensionSets[gfmRadio.id];
_renderMarkdown();
@@ -55,8 +59,8 @@
gfmRadio.onClick.listen(_switchFlavor);
}
-void _renderMarkdown([Event event]) {
- var markdown = markdownInput.value;
+void _renderMarkdown([Event? event]) {
+ var markdown = markdownInput.value!;
htmlDiv.setInnerHtml(md.markdownToHtml(markdown, extensionSet: extensionSet),
treeSanitizer: nullSanitizer);
@@ -77,9 +81,9 @@
}
void _typeItOut(String msg, int pos) {
- Timer timer;
+ late Timer timer;
markdownInput.onKeyUp.listen((_) {
- timer?.cancel();
+ timer.cancel();
});
void addCharacter() {
if (pos > msg.length) {
@@ -100,19 +104,19 @@
if (!target.attributes.containsKey('checked')) {
if (basicRadio != target) {
basicRadio.attributes.remove('checked');
- basicRadio.querySelector('.glyph').text = 'radio_button_unchecked';
+ basicRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
if (commonmarkRadio != target) {
commonmarkRadio.attributes.remove('checked');
- commonmarkRadio.querySelector('.glyph').text = 'radio_button_unchecked';
+ commonmarkRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
if (gfmRadio != target) {
gfmRadio.attributes.remove('checked');
- gfmRadio.querySelector('.glyph').text = 'radio_button_unchecked';
+ gfmRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
}
target.attributes['checked'] = '';
- target.querySelector('.glyph').text = 'radio_button_checked';
+ target.querySelector('.glyph')!.text = 'radio_button_checked';
extensionSet = extensionSets[target.id];
_renderMarkdown();
}
diff --git a/lib/src/ast.dart b/lib/src/ast.dart
index 5755226..8488a44 100644
--- a/lib/src/ast.dart
+++ b/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.
-typedef Resolver = Node Function(String name, [String title]);
+typedef Resolver = Node? Function(String name, [String? title]);
/// Base class for any AST item.
///
@@ -16,9 +16,9 @@
/// A named tag that can contain other nodes.
class Element implements Node {
final String tag;
- final List<Node> children;
+ final List<Node>? children;
final Map<String, String> attributes;
- String generatedId;
+ String? generatedId;
/// Instantiates a [tag] Element with [children].
Element(this.tag, this.children) : attributes = <String, String>{};
@@ -45,7 +45,7 @@
void accept(NodeVisitor visitor) {
if (visitor.visitElementBefore(this)) {
if (children != null) {
- for (var child in children) {
+ for (var child in children!) {
child.accept(visitor);
}
}
@@ -55,7 +55,7 @@
@override
String get textContent {
- return (children ?? []).map((Node child) => child.textContent).join('');
+ return (children ?? []).map((Node? child) => child!.textContent).join('');
}
}
diff --git a/lib/src/block_parser.dart b/lib/src/block_parser.dart
index 004c02f..49ee387 100644
--- a/lib/src/block_parser.dart
+++ b/lib/src/block_parser.dart
@@ -107,7 +107,7 @@
String get current => lines[_pos];
/// Gets the line after the current one or `null` if there is none.
- String get next {
+ String? get next {
// Don't read past the end.
if (_pos >= lines.length - 1) return null;
return lines[_pos + 1];
@@ -119,7 +119,7 @@
/// `peek(0)` is equivalent to [current].
///
/// `peek(1)` is equivalent to [next].
- String peek(int linesAhead) {
+ String? peek(int linesAhead) {
if (linesAhead < 0) {
throw ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
}
@@ -143,7 +143,7 @@
/// Gets whether or not the next line matches the given pattern.
bool matchesNext(RegExp regex) {
if (next == null) return false;
- return regex.hasMatch(next);
+ return regex.hasMatch(next!);
}
List<Node> parseLines() {
@@ -174,11 +174,11 @@
return pattern.hasMatch(parser.current);
}
- Node parse(BlockParser parser);
+ Node? parse(BlockParser parser);
- List<String> parseChildLines(BlockParser parser) {
+ List<String?> parseChildLines(BlockParser parser) {
// Grab all of the lines that form the block element.
- var childLines = <String>[];
+ var childLines = <String?>[];
while (!parser.isDone) {
var match = pattern.firstMatch(parser.current);
@@ -199,7 +199,7 @@
/// Generates a valid HTML anchor from the inner text of [element].
static String generateAnchorHash(Element element) =>
- element.children.first.textContent
+ element.children!.first.textContent
.toLowerCase()
.trim()
.replaceAll(RegExp(r'[^a-z0-9 _-]'), '')
@@ -213,7 +213,7 @@
const EmptyBlockSyntax();
@override
- Node parse(BlockParser parser) {
+ Node? parse(BlockParser parser) {
parser.encounteredBlankLine = true;
parser.advance();
@@ -253,7 +253,7 @@
@override
Node parse(BlockParser parser) {
var lines = <String>[];
- String tag;
+ String? tag;
while (!parser.isDone) {
var match = _setextPattern.firstMatch(parser.current);
if (match == null) {
@@ -263,7 +263,7 @@
continue;
} else {
// The underline.
- tag = (match[1][0] == '=') ? 'h1' : 'h2';
+ tag = (match[1]![0] == '=') ? 'h1' : 'h2';
parser.advance();
break;
}
@@ -271,7 +271,7 @@
var contents = UnparsedContent(lines.join('\n').trimRight());
- return Element(tag /*!*/, [contents]);
+ return Element(tag!, [contents]);
}
bool _interperableAsParagraph(String line) =>
@@ -307,10 +307,10 @@
@override
Node parse(BlockParser parser) {
- var match = pattern.firstMatch(parser.current);
+ var match = pattern.firstMatch(parser.current)!;
parser.advance();
- var level = match[1].length;
- var contents = UnparsedContent(match[2].trim());
+ var level = match[1]!.length;
+ var contents = UnparsedContent(match[2]!.trim());
return Element('h$level', [contents]);
}
}
@@ -342,7 +342,7 @@
while (!parser.isDone) {
var match = pattern.firstMatch(parser.current);
if (match != null) {
- childLines.add(match[1] /*!*/);
+ childLines.add(match[1]!);
parser.advance();
continue;
}
@@ -384,8 +384,8 @@
const CodeBlockSyntax();
@override
- List<String> parseChildLines(BlockParser parser) {
- var childLines = <String>[];
+ List<String?> parseChildLines(BlockParser parser) {
+ var childLines = <String?>[];
while (!parser.isDone) {
var match = pattern.firstMatch(parser.current);
@@ -396,7 +396,7 @@
// If there's a codeblock, then a newline, then a codeblock, keep the
// code blocks together.
var nextMatch =
- parser.next != null ? pattern.firstMatch(parser.next) : null;
+ parser.next != null ? pattern.firstMatch(parser.next!) : null;
if (parser.current.trim() == '' && nextMatch != null) {
childLines.add('');
childLines.add(nextMatch[1]);
@@ -439,18 +439,18 @@
bool canParse(BlockParser parser) {
final match = pattern.firstMatch(parser.current);
if (match == null) return false;
- final codeFence = match.group(1);
+ 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));
+ !infoString!.codeUnits.contains($backquote));
}
@override
- List<String> parseChildLines(BlockParser parser, [String endBlock]) {
+ List<String> parseChildLines(BlockParser parser, [String? endBlock]) {
endBlock ??= '';
var childLines = <String>[];
@@ -458,7 +458,7 @@
while (!parser.isDone) {
var match = pattern.firstMatch(parser.current);
- if (match == null || !match[1].startsWith(endBlock)) {
+ if (match == null || !match[1]!.startsWith(endBlock)) {
childLines.add(parser.current);
parser.advance();
} else {
@@ -473,9 +473,9 @@
@override
Node parse(BlockParser parser) {
// Get the syntax identifier, if there is one.
- var match = pattern.firstMatch(parser.current);
+ var match = pattern.firstMatch(parser.current)!;
var endBlock = match.group(1);
- var infoString = match.group(2);
+ var infoString = match.group(2)!;
var childLines = parseChildLines(parser, endBlock);
@@ -641,7 +641,7 @@
// Ideally, [BlockSyntax.canEndBlock] should be changed to be a method
// which accepts a [BlockParser], but this would be a breaking change,
// so we're going with this temporarily.
- var match = pattern.firstMatch(parser.current);
+ var match = pattern.firstMatch(parser.current)!;
// The seventh group, in both [_olPattern] and [_ulPattern] is the text
// after the delimiter.
return match[7]?.isNotEmpty ?? false;
@@ -675,20 +675,20 @@
}
}
- /*late*/ Match match;
+ late Match? match;
bool tryMatch(RegExp pattern) {
match = pattern.firstMatch(parser.current);
return match != null;
}
- String listMarker;
- String indent;
+ String? listMarker;
+ String? indent;
// In case the first number in an ordered list is not 1, use it as the
// "start".
- int startNumber;
+ int? startNumber;
while (!parser.isDone) {
- var leadingSpace = _whitespaceRe.matchAsPrefix(parser.current).group(0);
+ var leadingSpace = _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!;
var leadingExpandedTabLength = _expandedTabLength(leadingSpace);
if (tryMatch(_emptyPattern)) {
if (_emptyPattern.hasMatch(parser.next ?? '')) {
@@ -707,15 +707,15 @@
// Horizontal rule takes precedence to a new list item.
break;
} else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
- var precedingWhitespace = match[1] /*!*/;
- var digits = match[2] ?? '';
+ var precedingWhitespace = match![1]!;
+ var digits = match![2] ?? '';
if (startNumber == null && digits.isNotEmpty) {
startNumber = int.parse(digits);
}
- var marker = match[3] /*!*/;
- var firstWhitespace = match[5] ?? '';
- var restWhitespace = match[6] ?? '';
- var content = match[7] ?? '';
+ var marker = match![3]!;
+ var firstWhitespace = match![5] ?? '';
+ var restWhitespace = match![6] ?? '';
+ var content = match![7] ?? '';
var isBlank = content.isEmpty;
if (listMarker != null && listMarker != marker) {
// Changing the bullet or ordered list delimiter starts a new list.
@@ -792,7 +792,7 @@
var child = children[i];
if (child is Element && child.tag == 'p') {
children.removeAt(i);
- children.insertAll(i, child.children);
+ children.insertAll(i, child.children!);
}
}
}
@@ -883,11 +883,11 @@
/// * a divider of hyphens and pipes (not rendered)
/// * many body rows of body cells (`<td>` cells)
@override
- Node parse(BlockParser parser) {
- var alignments = _parseAlignments(parser.next);
+ Node? parse(BlockParser parser) {
+ var alignments = _parseAlignments(parser.next!);
var columnCount = alignments.length;
var headRow = _parseRow(parser, alignments, 'th');
- if (headRow.children.length != columnCount) {
+ if (headRow.children!.length != columnCount) {
return null;
}
var head = Element('thead', [headRow]);
@@ -908,8 +908,8 @@
children.removeLast();
}
}
- while (row.children.length > columnCount) {
- row.children.removeLast();
+ while (row.children!.length > columnCount) {
+ row.children!.removeLast();
}
rows.add(row);
}
@@ -922,7 +922,7 @@
}
}
- List<String> _parseAlignments(String line) {
+ List<String?> _parseAlignments(String line) {
var startIndex = _walkPastOpeningPipe(line);
var endIndex = line.length - 1;
@@ -954,7 +954,7 @@
/// [alignments] is used to annotate an alignment on each cell, and
/// [cellType] is used to declare either "td" or "th" cells.
Element _parseRow(
- BlockParser parser, List<String> alignments, String cellType) {
+ BlockParser parser, List<String?> alignments, String cellType) {
var line = parser.current;
var cells = <String>[];
var index = _walkPastOpeningPipe(line);
@@ -1098,7 +1098,7 @@
/// Extract reference link definitions from the front of the paragraph, and
/// return the remaining paragraph lines.
- List<String> _extractReflinkDefinitions(
+ List<String>? _extractReflinkDefinitions(
BlockParser parser, List<String> lines) {
bool lineStartsReflinkDefinition(int i) =>
lines[i].startsWith(_reflinkDefinitionStart);
@@ -1196,13 +1196,13 @@
// Not a reference link definition.
return false;
}
- if (match[0].length < contents.length) {
+ if (match.match.length < contents.length) {
// Trailing text. No good.
return false;
}
- var label = match[1];
- var destination = (match[2] ?? match[3]) /*!*/;
+ var label = match[1]!;
+ var destination = match[2] ?? match[3]!;
var title = match[4];
// The label must contain at least one non-whitespace character.
@@ -1215,7 +1215,7 @@
title = null;
} else {
// Remove "", '', or ().
- title = title.substring(1, title.length - 1);
+ title = title!.substring(1, title.length - 1);
}
// References are case-insensitive, and internal whitespace is compressed.
diff --git a/lib/src/document.dart b/lib/src/document.dart
index 2d57a46..550331a 100644
--- a/lib/src/document.dart
+++ b/lib/src/document.dart
@@ -11,8 +11,8 @@
class Document {
final Map<String, LinkReference> linkReferences = <String, LinkReference>{};
final ExtensionSet extensionSet;
- final Resolver linkResolver;
- final Resolver imageLinkResolver;
+ final Resolver? linkResolver;
+ final Resolver? imageLinkResolver;
final bool encodeHtml;
final _blockSyntaxes = <BlockSyntax>{};
final _inlineSyntaxes = <InlineSyntax>{};
@@ -22,9 +22,9 @@
Iterable<InlineSyntax> get inlineSyntaxes => _inlineSyntaxes;
Document({
- Iterable<BlockSyntax> blockSyntaxes,
- Iterable<InlineSyntax> inlineSyntaxes,
- ExtensionSet extensionSet,
+ Iterable<BlockSyntax>? blockSyntaxes,
+ Iterable<InlineSyntax>? inlineSyntaxes,
+ ExtensionSet? extensionSet,
this.linkResolver,
this.imageLinkResolver,
this.encodeHtml = true,
@@ -45,7 +45,7 @@
}
/// Parses the given inline Markdown [text] to a series of AST nodes.
- List<Node> parseInline(String text) => InlineParser(text, this).parse();
+ List<Node> parseInline(String? text) => InlineParser(text, this).parse();
void _parseInlineContent(List<Node> nodes) {
for (var i = 0; i < nodes.length; i++) {
@@ -56,7 +56,7 @@
nodes.insertAll(i, inlineNodes);
i += inlineNodes.length - 1;
} else if (node is Element && node.children != null) {
- _parseInlineContent(node.children);
+ _parseInlineContent(node.children!);
}
}
}
@@ -76,7 +76,7 @@
final String destination;
/// The [link title](http://spec.commonmark.org/0.28/#link-title).
- final String title;
+ final String? title;
/// Construct a new [LinkReference], with all necessary fields.
///
diff --git a/lib/src/html_renderer.dart b/lib/src/html_renderer.dart
index 77dabed..78ce279 100644
--- a/lib/src/html_renderer.dart
+++ b/lib/src/html_renderer.dart
@@ -13,11 +13,11 @@
/// Converts the given string of Markdown to HTML.
String markdownToHtml(
String markdown, {
- Iterable<BlockSyntax> blockSyntaxes,
- Iterable<InlineSyntax> inlineSyntaxes,
- ExtensionSet extensionSet,
- Resolver linkResolver,
- Resolver imageLinkResolver,
+ Iterable<BlockSyntax> blockSyntaxes = const [],
+ Iterable<InlineSyntax> inlineSyntaxes = const [],
+ ExtensionSet? extensionSet,
+ Resolver? linkResolver,
+ Resolver? imageLinkResolver,
bool inlineOnly = false,
}) {
var document = Document(
@@ -37,7 +37,7 @@
}
/// Renders [nodes] to HTML.
-String renderToHtml(List<Node> nodes) => HtmlRenderer().render(nodes);
+String renderToHtml(List<Node?> nodes) => HtmlRenderer().render(nodes);
const _blockTags = [
'blockquote',
@@ -74,20 +74,20 @@
/// Translates a parsed AST to HTML.
class HtmlRenderer implements NodeVisitor {
- StringBuffer buffer;
- Set<String> uniqueIds;
+ late StringBuffer buffer;
+ late Set<String> uniqueIds;
final _elementStack = <Element>[];
- String _lastVisitedTag;
+ String? _lastVisitedTag;
HtmlRenderer();
- String render(List<Node> nodes) {
+ String render(List<Node?> nodes) {
buffer = StringBuffer();
uniqueIds = <String>{};
for (final node in nodes) {
- node.accept(this);
+ node!.accept(this);
}
return buffer.toString();
@@ -123,9 +123,11 @@
buffer.write(' ${entry.key}="${entry.value}"');
}
+ var generatedId = element.generatedId;
+
// attach header anchor ids generated from text
- if (element.generatedId != null) {
- buffer.write(' id="${uniquifyId(element.generatedId)}"');
+ if (generatedId != null) {
+ buffer.write(' id="${uniquifyId(generatedId)}"');
}
_lastVisitedTag = element.tag;
@@ -151,7 +153,7 @@
assert(identical(_elementStack.last, element));
if (element.children != null &&
- element.children.isNotEmpty &&
+ element.children!.isNotEmpty &&
_blockTags.contains(_lastVisitedTag) &&
_blockTags.contains(element.tag)) {
buffer.writeln();
diff --git a/lib/src/inline_parser.dart b/lib/src/inline_parser.dart
index b7797f3..ccdb3d2 100644
--- a/lib/src/inline_parser.dart
+++ b/lib/src/inline_parser.dart
@@ -3,7 +3,6 @@
// 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';
@@ -47,7 +46,7 @@
]);
/// The string of Markdown being parsed.
- final String source;
+ final String? source;
/// The Markdown document this parser is parsing.
final Document document;
@@ -214,7 +213,7 @@
continue;
}
openersBottom.putIfAbsent(closer.char, () => List.filled(3, bottomIndex));
- var openersBottomPerCloserLength = openersBottom[closer.char];
+ var openersBottomPerCloserLength = openersBottom[closer.char]!;
var openerBottom = openersBottomPerCloserLength[closer.length % 3];
var openerIndex = _delimiterStack.lastIndexWhere(
(d) =>
@@ -233,8 +232,8 @@
_tree.sublist(openerTextNodeIndex + 1, closerTextNodeIndex));
// Replace all of the nodes between the opener and the closer (which
// are now the new emphasis node's children) with the emphasis node.
- _tree
- .replaceRange(openerTextNodeIndex + 1, closerTextNodeIndex, [node]);
+ _tree.replaceRange(
+ openerTextNodeIndex + 1, closerTextNodeIndex, [node!]);
// Slide [closerTextNodeIndex] back accordingly.
closerTextNodeIndex = openerTextNodeIndex + 2;
@@ -288,19 +287,19 @@
// Combine any remaining adjacent Text nodes. This is important to produce
// correct output across newlines, where whitespace is sometimes compressed.
- void _combineAdjacentText(List<Node> nodes) {
+ void _combineAdjacentText(List<Node?> nodes) {
for (var i = 0; i < nodes.length - 1; i++) {
var node = nodes[i];
if (node is Element && node.children != null) {
- _combineAdjacentText(node.children);
+ _combineAdjacentText(node.children!);
continue;
}
if (node is Text && nodes[i + 1] is Text) {
var buffer =
- StringBuffer('${node.textContent}${nodes[i + 1].textContent}');
+ StringBuffer('${node.textContent}${nodes[i + 1]!.textContent}');
var j = i + 2;
while (j < nodes.length && nodes[j] is Text) {
- buffer.write(nodes[j].textContent);
+ buffer.write(nodes[j]!.textContent);
j++;
}
nodes[i] = Text(buffer.toString());
@@ -309,13 +308,13 @@
}
}
- int charAt(int index) => source.codeUnitAt(index);
+ int charAt(int index) => source!.codeUnitAt(index);
void writeText() {
if (pos == start) {
return;
}
- var text = source.substring(start, pos);
+ var text = source!.substring(start, pos);
_tree.add(Text(text));
start = pos;
}
@@ -328,7 +327,7 @@
/// Push [state] onto the stack of [TagState]s.
void _pushDelimiter(Delimiter delimiter) => _delimiterStack.add(delimiter);
- bool get isDone => pos == source.length;
+ bool get isDone => pos == source!.length;
void advanceBy(int length) {
pos += length;
@@ -346,13 +345,13 @@
/// The first character of [pattern], to be used as an efficient first check
/// that this syntax matches the current parser position.
- final int _startCharacter;
+ final int? _startCharacter;
/// Create a new [InlineSyntax] which matches text on [pattern].
///
/// If [startCharacter] is passed, it is used as a pre-matching check which
/// is faster than matching against [pattern].
- InlineSyntax(String pattern, {int startCharacter})
+ InlineSyntax(String pattern, {int? startCharacter})
: pattern = RegExp(pattern, multiLine: true),
_startCharacter = startCharacter;
@@ -360,24 +359,24 @@
///
/// The parser's position can be overriden with [startMatchPos].
/// Returns whether or not the pattern successfully matched.
- bool tryMatch(InlineParser parser, [int startMatchPos]) {
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
startMatchPos ??= parser.pos;
// Before matching with the regular expression [pattern], which can be
// expensive on some platforms, check if even the first character matches
// this syntax.
if (_startCharacter != null &&
- parser.source.codeUnitAt(startMatchPos) != _startCharacter) {
+ parser.source!.codeUnitAt(startMatchPos) != _startCharacter) {
return false;
}
- final startMatch = pattern.matchAsPrefix(parser.source, startMatchPos);
+ final startMatch = pattern.matchAsPrefix(parser.source!, startMatchPos);
if (startMatch == null) return false;
// 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.match.length);
return true;
}
@@ -409,7 +408,7 @@
/// If [sub] is passed, it is used as a simple replacement for [pattern]. If
/// [startCharacter] is passed, it is used as a pre-matching check which is
/// faster than matching against [pattern].
- TextSyntax(String pattern, {String sub, int startCharacter})
+ TextSyntax(String pattern, {String sub = '', int? startCharacter})
: substitute = sub,
super(pattern, startCharacter: startCharacter);
@@ -420,11 +419,11 @@
/// returned.
@override
bool onMatch(InlineParser parser, Match match) {
- if (substitute == null ||
+ if (substitute.isEmpty ||
(match.start > 0 &&
match.input.substring(match.start - 1, match.start) == '/')) {
// Just use the original matched text.
- parser.advanceBy(match[0].length);
+ parser.advanceBy(match.match.length);
return false;
}
@@ -440,7 +439,8 @@
@override
bool onMatch(InlineParser parser, Match match) {
- final char = match[0].codeUnitAt(1);
+ var chars = match.match;
+ var char = chars.codeUnitAt(1);
// Insert the substitution. Why these three charactes are replaced with
// their equivalent HTML entity referenced appears to be missing from the
// CommonMark spec, but is very present in all of the examples.
@@ -452,7 +452,7 @@
} else if (char == $gt) {
parser.addNode(Text('>'));
} else {
- parser.addNode(Text(match[0][1]));
+ parser.addNode(Text(chars[1]));
}
return true;
}
@@ -485,7 +485,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');
@@ -501,7 +501,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(url);
@@ -544,13 +544,13 @@
AutolinkExtensionSyntax() : super('$start(($scheme)($domain)($path))');
@override
- bool tryMatch(InlineParser parser, [int startMatchPos]) {
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
return super.tryMatch(parser, parser.pos > 0 ? parser.pos - 1 : 0);
}
@override
bool onMatch(InlineParser parser, Match match) {
- var url = match[1];
+ var url = match[1]!;
var href = url;
var matchLength = url.length;
@@ -562,7 +562,7 @@
}
// Prevent accidental standard autolink matches
- if (url.endsWith('>') && parser.source[parser.pos - 1] == '<') {
+ if (url.endsWith('>') && parser.source![parser.pos - 1] == '<') {
return false;
}
@@ -589,7 +589,7 @@
// https://github.github.com/gfm/#example-599
final trailingPunc = regExpTrailingPunc.firstMatch(url);
if (trailingPunc != null) {
- var trailingLength = trailingPunc[0].length;
+ var trailingLength = trailingPunc.match.length;
url = url.substring(0, url.length - trailingLength);
href = href.substring(0, href.length - trailingLength);
matchLength -= trailingLength;
@@ -605,7 +605,7 @@
final entityRef = regExpEndsWithColon.firstMatch(url);
if (entityRef != null) {
// Strip out HTML entity reference
- var entityRefLength = entityRef[0].length;
+ var entityRefLength = entityRef.match.length;
url = url.substring(0, url.length - entityRefLength);
href = href.substring(0, href.length - entityRefLength);
matchLength -= entityRefLength;
@@ -643,7 +643,7 @@
/// a [TagSyntax].
abstract class Delimiter {
/// The [Text] node representing the plain text representing this delimiter.
- Text node;
+ abstract Text node;
/// The type of delimiter.
///
@@ -664,7 +664,7 @@
/// stack. It is, by default, active. Once we parse the next possible link,
/// `[more](links)`, as a real link, we must deactive the pending links (just
/// the one, in this case).
- bool isActive;
+ abstract bool isActive;
/// Whether this delimiter can open emphasis or strong emphasis.
bool get canOpen;
@@ -703,13 +703,13 @@
final int endPos;
SimpleDelimiter(
- {@required this.node,
- @required this.char,
- @required this.length,
- @required this.canOpen,
- @required this.canClose,
- @required this.syntax,
- @required this.endPos})
+ {required this.node,
+ required this.char,
+ required this.length,
+ required this.canOpen,
+ required this.canClose,
+ required this.syntax,
+ required this.endPos})
: isActive = true;
}
@@ -778,15 +778,15 @@
final bool canClose;
DelimiterRun._({
- @required this.node,
- @required this.char,
- @required this.syntax,
- @required bool isLeftFlanking,
- @required bool isRightFlanking,
- @required bool isPrecededByPunctuation,
- @required bool isFollowedByPunctuation,
- @required this.allowIntraWord,
- }) : canOpen = isLeftFlanking &&
+ required this.node,
+ required this.char,
+ required this.syntax,
+ required bool isLeftFlanking,
+ required bool isRightFlanking,
+ required bool isPrecededByPunctuation,
+ required bool isFollowedByPunctuation,
+ required this.allowIntraWord,
+ }) : canOpen = isLeftFlanking &&
(char == $asterisk ||
!isRightFlanking ||
allowIntraWord ||
@@ -800,9 +800,9 @@
/// Tries to parse a delimiter run from [runStart] (inclusive) to [runEnd]
/// (exclusive).
- static DelimiterRun tryParse(InlineParser parser, int runStart, int runEnd,
- {@required TagSyntax syntax,
- @required Text node,
+ static DelimiterRun? tryParse(InlineParser parser, int runStart, int runEnd,
+ {required TagSyntax syntax,
+ required Text node,
bool allowIntraWord = false}) {
bool leftFlanking,
rightFlanking,
@@ -813,15 +813,15 @@
rightFlanking = false;
preceding = '\n';
} else {
- preceding = parser.source.substring(runStart - 1, runStart);
+ preceding = parser.source!.substring(runStart - 1, runStart);
}
precededByPunctuation = punctuation.hasMatch(preceding);
- if (runEnd == parser.source.length) {
+ if (runEnd == parser.source!.length) {
leftFlanking = false;
following = '\n';
} else {
- following = parser.source.substring(runEnd, runEnd + 1);
+ following = parser.source!.substring(runEnd, runEnd + 1);
}
followedByPunctuation = punctuation.hasMatch(following);
@@ -890,21 +890,21 @@
/// pre-matching check which is faster than matching against [pattern].
TagSyntax(String pattern,
{this.requiresDelimiterRun = false,
- int startCharacter,
+ int? startCharacter,
this.allowIntraWord = false})
: super(pattern, startCharacter: startCharacter);
@override
bool onMatch(InlineParser parser, Match match) {
- var runLength = match.group(0).length;
+ var runLength = match.group(0)!.length;
var matchStart = parser.pos;
var matchEnd = parser.pos + runLength;
- var text = Text(parser.source.substring(matchStart, matchEnd));
+ var text = Text(parser.source!.substring(matchStart, matchEnd));
if (!requiresDelimiterRun) {
parser._pushDelimiter(SimpleDelimiter(
node: text,
length: runLength,
- char: parser.source.codeUnitAt(matchStart),
+ char: parser.source!.codeUnitAt(matchStart),
canOpen: true,
canClose: false,
syntax: this,
@@ -934,8 +934,8 @@
/// If a tag can be closed at the current position, then this method calls
/// [getChildren], in which [parser] parses any nested text into child nodes.
/// The returned [Node] incorpororates these child nodes.
- Node close(InlineParser parser, Delimiter opener, Delimiter closer,
- {@required List<Node> Function() getChildren}) {
+ Node? close(InlineParser parser, Delimiter opener, Delimiter closer,
+ {required List<Node> Function() getChildren}) {
var strong = opener.length >= 2 && closer.length >= 2;
return Element(strong ? 'strong' : 'em', getChildren());
}
@@ -948,7 +948,7 @@
@override
Node close(InlineParser parser, Delimiter opener, Delimiter closer,
- {@required List<Node> Function() getChildren}) {
+ {required List<Node> Function() getChildren}) {
return Element('del', getChildren());
}
}
@@ -960,21 +960,21 @@
final Resolver linkResolver;
LinkSyntax(
- {Resolver linkResolver,
+ {Resolver? linkResolver,
String pattern = r'\[',
int startCharacter = $lbracket})
- : linkResolver = (linkResolver ?? (String _, [String __]) => null),
+ : linkResolver = (linkResolver ?? ((String _, [String? __]) => null)),
super(pattern, startCharacter: startCharacter);
@override
- Node close(
- InlineParser parser, covariant SimpleDelimiter opener, Delimiter closer,
- {@required List<Node> Function() getChildren}) {
- var text = parser.source.substring(opener.endPos, parser.pos);
+ Node? close(
+ InlineParser parser, covariant SimpleDelimiter opener, Delimiter? closer,
+ {required List<Node> Function() getChildren}) {
+ var text = parser.source!.substring(opener.endPos, parser.pos);
// The current character is the `]` that closed the link text. Examine the
// next character, to determine what type of link we might have (a '('
// means a possible inline link; otherwise a possible reference link).
- if (parser.pos + 1 >= parser.source.length) {
+ if (parser.pos + 1 >= parser.source!.length) {
// The `]` is at the end of the document, but this may still be a valid
// shortcut reference link.
return _tryCreateReferenceLink(parser, text, getChildren: getChildren);
@@ -1007,7 +1007,7 @@
parser.advanceBy(1);
// At this point, we've matched `[...][`. Maybe a *full* reference link,
// like `[foo][bar]` or a *collapsed* reference link, like `[foo][]`.
- if (parser.pos + 1 < parser.source.length &&
+ if (parser.pos + 1 < parser.source!.length &&
parser.charAt(parser.pos + 1) == $rbracket) {
// That opening `[` is not actually part of the link. Maybe a
// *shortcut* reference link (followed by a `[`).
@@ -1036,13 +1036,13 @@
/// Otherwise, returns `null`.
///
/// [label] does not need to be normalized.
- Node _resolveReferenceLink(
+ Node? _resolveReferenceLink(
String label, Map<String, LinkReference> linkReferences,
- {List<Node> Function() getChildren}) {
+ {List<Node> Function()? getChildren}) {
var linkReference = linkReferences[normalizeLinkLabel(label)];
if (linkReference != null) {
return _createNode(linkReference.destination, linkReference.title,
- getChildren: getChildren);
+ getChildren: getChildren!);
} else {
// This link has no reference definition. But we allow users of the
// library to specify a custom resolver function ([linkResolver]) that
@@ -1057,15 +1057,15 @@
.replaceAll(r'\[', '[')
.replaceAll(r'\]', ']'));
if (resolved != null) {
- getChildren();
+ getChildren!();
}
return resolved;
}
}
/// Create the node represented by a Markdown link.
- Node _createNode(String destination, String title,
- {@required List<Node> Function() getChildren}) {
+ Node _createNode(String destination, String? title,
+ {required List<Node> Function() getChildren}) {
var children = getChildren();
var element = Element('a', children);
element.attributes['href'] = escapeAttribute(destination);
@@ -1077,18 +1077,18 @@
/// Tries to create a reference link node.
///
- /// Returns whether the link was created successfully.
- Node _tryCreateReferenceLink(InlineParser parser, String label,
- {List<Node> Function() getChildren}) {
+ /// Returns the link if it was successfully created, `null` otherwise.
+ Node? _tryCreateReferenceLink(InlineParser parser, String label,
+ {required List<Node> Function() getChildren}) {
return _resolveReferenceLink(label, parser.document.linkReferences,
getChildren: getChildren);
}
// Tries to create an inline link node.
//
- // Returns whether the link was created successfully.
+ /// Returns the link if it was successfully created, `null` otherwise.
Node _tryCreateInlineLink(InlineParser parser, InlineLink link,
- {List<Node> Function() getChildren}) {
+ {required List<Node> Function() getChildren}) {
return _createNode(link.destination, link.title, getChildren: getChildren);
}
@@ -1098,7 +1098,7 @@
/// opens the link label.
///
/// Returns the label if it could be parsed, or `null` if not.
- String _parseReferenceLinkLabel(InlineParser parser) {
+ String? _parseReferenceLinkLabel(InlineParser parser) {
// Walk past the opening `[`.
parser.advanceBy(1);
if (parser.isDone) return null;
@@ -1141,7 +1141,7 @@
/// `(http://url "title")`.
///
/// Returns the [InlineLink] if one was parsed, or `null` if not.
- InlineLink _parseInlineLink(InlineParser parser) {
+ InlineLink? _parseInlineLink(InlineParser parser) {
// Start walking to the character just after the opening `(`.
parser.advanceBy(1);
@@ -1159,7 +1159,9 @@
/// Parse an inline link with a bracketed destination (a destination wrapped
/// in `<...>`). The current position of the parser must be the first
/// character of the destination.
- InlineLink _parseInlineBracketedLink(InlineParser parser) {
+ ///
+ /// Returns the link if it was successfully created, `null` otherwise.
+ InlineLink? _parseInlineBracketedLink(InlineParser parser) {
parser.advanceBy(1);
var buffer = StringBuffer();
@@ -1212,7 +1214,9 @@
/// Parse an inline link with a "bare" destination (a destination _not_
/// wrapped in `<...>`). The current position of the parser must be the first
/// character of the destination.
- InlineLink _parseInlineBareDestinationLink(InlineParser parser) {
+ ///
+ /// Returns the link if it was successfully created, `null` otherwise.
+ InlineLink? _parseInlineBareDestinationLink(InlineParser parser) {
// According to
// [CommonMark](http://spec.commonmark.org/0.28/#link-destination):
//
@@ -1301,10 +1305,12 @@
}
}
- // Parse a link title in [parser] at it's current position. The parser's
- // current position should be a whitespace character that followed a link
- // destination.
- String _parseTitle(InlineParser parser) {
+ /// Parses a link title in [parser] at it's current position. The parser's
+ /// current position should be a whitespace character that followed a link
+ /// destination.
+ ///
+ /// Returns the title if it was successfully parsed, `null` otherwise.
+ String? _parseTitle(InlineParser parser) {
_moveThroughWhitespace(parser);
if (parser.isDone) return null;
@@ -1353,15 +1359,15 @@
/// Matches images like `![alternate text](url "optional title")` and
/// `![alternate text][label]`.
class ImageSyntax extends LinkSyntax {
- ImageSyntax({Resolver linkResolver})
+ ImageSyntax({Resolver? linkResolver})
: super(
linkResolver: linkResolver,
pattern: r'!\[',
startCharacter: $exclamation);
@override
- Element _createNode(String destination, String title,
- {List<Node> Function() getChildren}) {
+ Element _createNode(String destination, String? title,
+ {required List<Node> Function() getChildren}) {
var element = Element.empty('img');
var children = getChildren();
element.attributes['src'] = destination;
@@ -1391,7 +1397,7 @@
CodeSyntax() : super(_pattern);
@override
- bool tryMatch(InlineParser parser, [int startMatchPos]) {
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) {
// Not really a match! We can't just sneak past one backtick to try the
// next character. An example of this situation would be:
@@ -1401,18 +1407,18 @@
return false;
}
- var match = pattern.matchAsPrefix(parser.source, parser.pos);
+ var match = pattern.matchAsPrefix(parser.source!, parser.pos);
if (match == null) {
return false;
}
parser.writeText();
- if (onMatch(parser, match)) parser.consume(match[0].length);
+ if (onMatch(parser, match)) parser.consume(match.match.length);
return true;
}
@override
bool onMatch(InlineParser parser, Match match) {
- var code = match[2].trim().replaceAll('\n', ' ');
+ var code = match[2]!.trim().replaceAll('\n', ' ');
if (parser.document.encodeHtml) code = escapeHtml(code);
parser.addNode(Element.text('code', code));
@@ -1432,7 +1438,7 @@
@override
bool onMatch(InlineParser parser, Match match) {
- var alias = match[1];
+ var alias = match[1]!;
var emoji = emojis[alias];
if (emoji == null) {
parser.advanceBy(1);
@@ -1446,7 +1452,7 @@
class InlineLink {
final String destination;
- final String title;
+ final String? title;
InlineLink(this.destination, {this.title});
}
diff --git a/lib/src/util.dart b/lib/src/util.dart
index 8a53657..3a8a454 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
import 'dart:convert';
import 'package:charcode/charcode.dart';
@@ -82,3 +86,8 @@
/// [CommonMark spec] https://spec.commonmark.org/0.29/#link-label
String normalizeLinkLabel(String label) =>
label.trim().replaceAll(_oneOrMoreWhitespacePattern, ' ').toLowerCase();
+
+extension MatchExtensions on Match {
+ /// Returns the whole match String
+ String get match => this[0]!;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 325b6a1..3a86167 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,12 +9,12 @@
markdown:
environment:
- sdk: '>=2.6.0 <3.0.0'
+ sdk: '>=2.12.0-0 <3.0.0'
dependencies:
args: ^1.0.0
- charcode: ^1.1.0
- meta: ^1.0.0
+ charcode: ^1.2.0-nullsafety.3
+ meta: ^1.3.0-nullsafety.6
dev_dependencies:
build_runner: ^1.0.0
@@ -24,7 +24,7 @@
html: '>=0.12.2 <0.15.0'
io: ^0.3.2+1
js: ^0.6.1
- path: ^1.3.1
- pedantic: ^1.9.0
- test: ^1.2.0
- yaml: ^2.1.8
+ path: ^1.8.0-nullsafety.3
+ pedantic: ^1.10.0-nullsafety.3
+ test: ^1.16.0-nullsafety.13
+ yaml: ^3.0.0-nullsafety.0
diff --git a/test/markdown_test.dart b/test/markdown_test.dart
index 03785c8..542a601 100644
--- a/test/markdown_test.dart
+++ b/test/markdown_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart=2.9
+
import 'package:markdown/markdown.dart';
import 'package:test/test.dart';
@@ -125,7 +127,7 @@
validateCore('dart custom links', 'links [are<foo>] awesome',
'<p>links <a>are<foo></a> awesome</p>\n',
- linkResolver: (String text, [_]) =>
+ linkResolver: (String text, [String /*?*/ _]) =>
Element.text('a', text.replaceAll('<', '<')));
// TODO(amouravski): need more tests here for custom syntaxes, as some
diff --git a/test/util.dart b/test/util.dart
index 90f0c74..8700160 100644
--- a/test/util.dart
+++ b/test/util.dart
@@ -2,8 +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.
-// Used by `dataCasesUnder` below to find the current directory.
-library markdown.test.util;
+// @dart=2.9
import 'dart:isolate';
@@ -27,13 +26,15 @@
}
}
+Future<String> get markdownPackageRoot async =>
+ p.dirname(p.dirname((await Isolate.resolvePackageUri(
+ Uri.parse('package:markdown/markdown.dart')))
+ .path));
+
void testFile(String file,
{Iterable<BlockSyntax> blockSyntaxes,
Iterable<InlineSyntax> inlineSyntaxes}) async {
- var markdownLibRoot = p.dirname((await Isolate.resolvePackageUri(
- Uri.parse('package:markdown/markdown.dart')))
- .path);
- var directory = p.join(p.dirname(markdownLibRoot), 'test');
+ var directory = p.join(await markdownPackageRoot, 'test');
for (var dataCase in dataCasesInFile(path: p.join(directory, file))) {
var description =
'${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
diff --git a/test/version_test.dart b/test/version_test.dart
index f9cb207..df2149a 100644
--- a/test/version_test.dart
+++ b/test/version_test.dart
@@ -1,24 +1,31 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.9
+
import 'dart:io';
-import 'dart:mirrors';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:yaml/yaml.dart';
-// Locate the "tool" directory. Use mirrors so that this works with the test
-// package, which loads this suite into an isolate.
-String get _currentDir => p
- .dirname((reflect(main) as ClosureMirror).function.location.sourceUri.path);
+import 'util.dart';
void main() {
- test('check versions', () {
- var binary = p.normalize(p.join(_currentDir, '..', 'bin/markdown.dart'));
- var result = Process.runSync('dart', [binary, '--version']);
- expect(result.exitCode, 0);
+ test('check versions', () async {
+ var packageRoot = await markdownPackageRoot;
+ var binary = p.normalize(p.join(packageRoot, 'bin', 'markdown.dart'));
+ var dartBin = Platform.executable;
+ var result = Process.runSync(dartBin, [binary, '--version']);
+ expect(result.exitCode, 0,
+ reason: 'Exit code expected: 0; actual: ${result.exitCode}\n\n'
+ 'stdout: ${result.stdout}\n\n'
+ 'stderr: ${result.stderr}');
var binVersion = (result.stdout as String).trim();
- var pubspecFile = p.normalize(p.join(_currentDir, '..', 'pubspec.yaml'));
+ var pubspecFile = p.normalize(p.join(packageRoot, 'pubspec.yaml'));
var pubspecContent =
loadYaml(File(pubspecFile).readAsStringSync()) as YamlMap;
diff --git a/tool/dartdoc-compare.dart b/tool/dartdoc-compare.dart
index 93857e6..dcd2bb0 100644
--- a/tool/dartdoc-compare.dart
+++ b/tool/dartdoc-compare.dart
@@ -1,3 +1,9 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.9
+
import 'dart:convert' show jsonEncode, jsonDecode;
import 'dart:io' show Directory, File, Platform, Process, exitCode;
diff --git a/tool/expected_output.dart b/tool/expected_output.dart
index a85ccde..8561c8e 100644
--- a/tool/expected_output.dart
+++ b/tool/expected_output.dart
@@ -5,11 +5,11 @@
import 'dart:io';
import 'dart:isolate';
-import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
/// Parse and yield data cases (each a [DataCase]) from [path].
-Iterable<DataCase> dataCasesInFile({String path, String baseDir}) sync* {
+Iterable<DataCase> dataCasesInFile(
+ {required String path, String? baseDir}) sync* {
var file = p.basename(path).replaceFirst(RegExp(r'\..+$'), '');
baseDir ??= p.relative(p.dirname(path), from: p.dirname(p.dirname(path)));
@@ -61,7 +61,7 @@
/// cases are read from files located immediately in [directory], or
/// recursively, according to [recursive].
Iterable<DataCase> _dataCases({
- String directory,
+ required String directory,
String extension = 'unit',
bool recursive = true,
}) {
@@ -113,12 +113,12 @@
/// }
/// ```
Stream<DataCase> dataCasesUnder({
- @required String testDirectory,
+ required String testDirectory,
String extension = 'unit',
bool recursive = true,
}) async* {
var markdownLibRoot = p.dirname((await Isolate.resolvePackageUri(
- Uri.parse('package:markdown/markdown.dart')))
+ Uri.parse('package:markdown/markdown.dart')))!
.path);
var directory =
p.joinAll([p.dirname(markdownLibRoot), 'test', testDirectory]);
@@ -142,14 +142,14 @@
final String expectedOutput;
DataCase({
- this.directory,
- this.file,
+ this.directory = '',
+ this.file = '',
// ignore: non_constant_identifier_names
- this.front_matter,
- this.description,
- this.skip,
- this.input,
- this.expectedOutput,
+ this.front_matter = '',
+ this.description = '',
+ this.skip = false,
+ required this.input,
+ required this.expectedOutput,
});
/// A good standard description for `test()`, derived from the data directory,
diff --git a/tool/stats.dart b/tool/stats.dart
index a5c9a3f..b8b4e76 100644
--- a/tool/stats.dart
+++ b/tool/stats.dart
@@ -1,3 +1,9 @@
+// Copyright (c) 2020, 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.
+
+// @dart=2.9
+
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
diff --git a/tool/stats_lib.dart b/tool/stats_lib.dart
index 11549df..a4a7e20 100644
--- a/tool/stats_lib.dart
+++ b/tool/stats_lib.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart=2.9
+
import 'dart:convert';
import 'dart:io';
import 'dart:mirrors';