Misc package cleanup, mostly lints (dart-lang/markdown#468)

Centralized HTTP JSON download logic
diff --git a/pkgs/markdown/analysis_options.yaml b/pkgs/markdown/analysis_options.yaml
index 4190e9a..ac272aa 100644
--- a/pkgs/markdown/analysis_options.yaml
+++ b/pkgs/markdown/analysis_options.yaml
@@ -1,4 +1,6 @@
+# https://dart.dev/guides/language/analysis-options
 include: package:lints/recommended.yaml
+
 analyzer:
   language:
     strict-inference: true
@@ -17,15 +19,37 @@
   rules:
     # https://github.com/dart-lang/linter/issues/574
     #- comment_references
+    - always_declare_return_types
+    - avoid_catching_errors
+    - avoid_dynamic_calls
+    - avoid_private_typedef_functions
+    - avoid_redundant_argument_values
+    - avoid_unused_constructor_parameters
+    - avoid_void_async
+    - cancel_subscriptions
     - directives_ordering
+    - literal_only_boolean_expressions
+    - missing_whitespace_between_adjacent_strings
+    - no_adjacent_strings_in_list
+    - omit_local_variable_types
     - only_throw_errors
     - package_api_docs
+    - prefer_asserts_in_initializer_lists
+    - prefer_const_constructors
     - prefer_const_declarations
     - prefer_final_locals
+    - prefer_relative_imports
     - prefer_single_quotes
+    - sort_pub_dependencies
     - test_types_in_equals
     - throw_in_finally
+    - type_annotate_public_apis
+    - unawaited_futures
+    - unnecessary_await_in_return
+    - unnecessary_lambdas
+    - unnecessary_parenthesis
     - unnecessary_raw_strings
     - use_if_null_to_convert_nulls_to_bools
     - use_raw_strings
+    - use_string_buffers
     - use_super_parameters
diff --git a/pkgs/markdown/benchmark/benchmark.dart b/pkgs/markdown/benchmark/benchmark.dart
index 4c4d61f..63a8d1f 100644
--- a/pkgs/markdown/benchmark/benchmark.dart
+++ b/pkgs/markdown/benchmark/benchmark.dart
@@ -38,7 +38,8 @@
     // the VM doesn't optimize "dead" code away.
     if (result != expected) {
       print('Incorrect output:\n$result');
-      exit(1);
+      exitCode = 1;
+      return;
     }
 
     // Don't print the first run. It's always terrible since the VM hasn't
diff --git a/pkgs/markdown/lib/src/ast.dart b/pkgs/markdown/lib/src/ast.dart
index c9fd8e7..3fb2561 100644
--- a/pkgs/markdown/lib/src/ast.dart
+++ b/pkgs/markdown/lib/src/ast.dart
@@ -58,7 +58,7 @@
     final children = this.children;
     return children == null
         ? ''
-        : children.map((child) => child.textContent).join('');
+        : children.map((child) => child.textContent).join();
   }
 }
 
diff --git a/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
index 7d16e55..00b7560 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
@@ -21,7 +21,7 @@
     // Grab all of the lines that form the blockquote, stripping off the ">".
     final childLines = <String>[];
 
-    bool encounteredCodeBlock = false;
+    var encounteredCodeBlock = false;
     while (!parser.isDone) {
       final match = pattern.firstMatch(parser.current);
       if (match != null) {
diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
index a64e031..e869196 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
@@ -28,8 +28,8 @@
     //
     // > 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));
+    return codeFence.codeUnitAt(0) != $backquote ||
+        !infoString!.codeUnits.contains($backquote);
   }
 
   @override
diff --git a/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart
index 4b7619f..7b6d0b1 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart
@@ -83,13 +83,13 @@
       final pattern = RegExp(r'^ {0,3}\[([ xX])\][ \t]');
 
       if (taskListParserEnabled && pattern.hasMatch(text)) {
-        return text.replaceFirstMapped(pattern, ((match) {
+        return text.replaceFirstMapped(pattern, (match) {
           taskListItemState = match[1] == ' '
               ? TaskListItemState.unchecked
               : TaskListItemState.checked;
 
           return '';
-        }));
+        });
       } else {
         taskListItemState = null;
         return text;
diff --git a/pkgs/markdown/lib/src/extension_set.dart b/pkgs/markdown/lib/src/extension_set.dart
index d9d2e38..a7bf1ee 100644
--- a/pkgs/markdown/lib/src/extension_set.dart
+++ b/pkgs/markdown/lib/src/extension_set.dart
@@ -1,11 +1,10 @@
-import 'package:markdown/src/block_syntaxes/ordered_list_with_checkbox_syntax.dart';
-import 'package:markdown/src/block_syntaxes/unordered_list_with_checkbox_syntax.dart';
-
 import 'block_syntaxes/block_syntax.dart';
 import 'block_syntaxes/fenced_code_block_syntax.dart';
 import 'block_syntaxes/header_with_id_syntax.dart';
+import 'block_syntaxes/ordered_list_with_checkbox_syntax.dart';
 import 'block_syntaxes/setext_header_with_id_syntax.dart';
 import 'block_syntaxes/table_syntax.dart';
+import 'block_syntaxes/unordered_list_with_checkbox_syntax.dart';
 import 'inline_syntaxes/autolink_extension_syntax.dart';
 import 'inline_syntaxes/color_swatch_syntax.dart';
 import 'inline_syntaxes/emoji_syntax.dart';
diff --git a/pkgs/markdown/lib/src/inline_parser.dart b/pkgs/markdown/lib/src/inline_parser.dart
index 603d3da..25275d9 100644
--- a/pkgs/markdown/lib/src/inline_parser.dart
+++ b/pkgs/markdown/lib/src/inline_parser.dart
@@ -153,7 +153,7 @@
       return;
     }
     final syntax = delimiter.syntax;
-    if (syntax is LinkSyntax && syntaxes.any(((e) => e is LinkSyntax))) {
+    if (syntax is LinkSyntax && syntaxes.any((e) => e is LinkSyntax)) {
       final nodeIndex = _tree.lastIndexWhere((n) => n == delimiter.node);
       final linkNode = syntax.close(this, delimiter, null, getChildren: () {
         _processDelimiterRun(index);
diff --git a/pkgs/markdown/test/document_test.dart b/pkgs/markdown/test/document_test.dart
index 42ca40f..a9a8be7 100644
--- a/pkgs/markdown/test/document_test.dart
+++ b/pkgs/markdown/test/document_test.dart
@@ -20,7 +20,7 @@
     });
 
     group('with encodeHtml enabled', () {
-      final document = Document(encodeHtml: true);
+      final document = Document();
 
       test('encodes HTML in an inline code snippet', () {
         final result =
@@ -55,7 +55,7 @@
       test('encodeHtml spaces are preserved in text', () {
         // Example to get a <p> tag rendered before a text node.
         const contents = 'Sample\n\n<pre>\n A\n B\n</pre>';
-        final document = Document(encodeHtml: true);
+        final document = Document();
         final lines = LineSplitter.split(contents).toList();
         final nodes = BlockParser(lines, document).parseLines();
         final result = HtmlRenderer().render(nodes);
@@ -65,7 +65,7 @@
       test('encode double quotes, greater than, and less than when escaped',
           () {
         const contents = r'\>\"\< Hello';
-        final document = Document(encodeHtml: true);
+        final document = Document();
         final nodes = document.parseInline(contents);
         expect(nodes, hasLength(1));
         expect(
diff --git a/pkgs/markdown/test/html_renderer_test.dart b/pkgs/markdown/test/html_renderer_test.dart
index cc32869..77c33d3 100644
--- a/pkgs/markdown/test/html_renderer_test.dart
+++ b/pkgs/markdown/test/html_renderer_test.dart
@@ -40,7 +40,6 @@
     test('with only default block syntaxes', () {
       final result = markdownToHtml(
         text,
-        withDefaultBlockSyntaxes: true,
         withDefaultInlineSyntaxes: false,
         encodeHtml: false,
       );
@@ -55,7 +54,6 @@
       final result = markdownToHtml(
         text,
         withDefaultBlockSyntaxes: false,
-        withDefaultInlineSyntaxes: true,
         encodeHtml: false,
       );
 
@@ -70,7 +68,6 @@
         text,
         withDefaultBlockSyntaxes: false,
         withDefaultInlineSyntaxes: false,
-        encodeHtml: true,
       );
 
       expect(
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index 0b05c73..30142df 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -8,7 +8,7 @@
 import 'util.dart';
 
 void main() async {
-  await testDirectory('original');
+  testDirectory('original');
 
   // Block syntax extensions.
   testFile(
@@ -54,8 +54,8 @@
     inlineSyntaxes: [StrikethroughSyntax()],
   );
 
-  await testDirectory('common_mark');
-  await testDirectory('gfm', extensionSet: ExtensionSet.gitHubFlavored);
+  testDirectory('common_mark');
+  testDirectory('gfm', extensionSet: ExtensionSet.gitHubFlavored);
 
   group('Corner cases', () {
     validateCore('Incorrect Links', '''
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart
index ad73c0c..b38b264 100644
--- a/pkgs/markdown/test/util.dart
+++ b/pkgs/markdown/test/util.dart
@@ -2,17 +2,16 @@
 // 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:isolate';
-
 import 'package:io/ansi.dart' as ansi;
 import 'package:markdown/markdown.dart';
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
+
 import '../tool/expected_output.dart';
 
 /// Runs tests defined in "*.unit" files inside directory [name].
-Future<void> testDirectory(String name, {ExtensionSet? extensionSet}) async {
-  await for (final dataCase in dataCasesUnder(testDirectory: name)) {
+void testDirectory(String name, {ExtensionSet? extensionSet}) {
+  for (final dataCase in dataCasesUnder(testDirectory: name)) {
     final description =
         '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
     validateCore(
@@ -24,19 +23,13 @@
   }
 }
 
-Future<String> get markdownPackageRoot async {
-  final packageUri = Uri.parse('package:markdown/markdown.dart');
-  final isolateUri = await Isolate.resolvePackageUri(packageUri);
-  return p.dirname(p.dirname(isolateUri!.toFilePath()));
-}
-
 void testFile(
   String file, {
   Iterable<BlockSyntax> blockSyntaxes = const [],
   Iterable<InlineSyntax> inlineSyntaxes = const [],
-}) async {
-  final directory = p.join(await markdownPackageRoot, 'test');
-  for (final dataCase in dataCasesInFile(path: p.join(directory, file))) {
+}) {
+  for (final dataCase
+      in dataCasesInFile(path: p.join(p.current, 'test', file))) {
     final description =
         '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
     validateCore(
diff --git a/pkgs/markdown/test/version_test.dart b/pkgs/markdown/test/version_test.dart
index 14763d3..4397ad1 100644
--- a/pkgs/markdown/test/version_test.dart
+++ b/pkgs/markdown/test/version_test.dart
@@ -8,12 +8,9 @@
 import 'package:test/test.dart';
 import 'package:yaml/yaml.dart';
 
-import 'util.dart';
-
 void main() {
   test('check versions', () async {
-    final packageRoot = await markdownPackageRoot;
-    final binary = p.normalize(p.join(packageRoot, 'bin', 'markdown.dart'));
+    final binary = p.join(p.current, 'bin', 'markdown.dart');
     final dartBin = Platform.executable;
     final result = Process.runSync(dartBin, [binary, '--version']);
     expect(
@@ -26,7 +23,7 @@
 
     final binVersion = (result.stdout as String).trim();
 
-    final pubspecFile = p.normalize(p.join(packageRoot, 'pubspec.yaml'));
+    final pubspecFile = p.join(p.current, 'pubspec.yaml');
 
     final pubspecContent =
         loadYaml(File(pubspecFile).readAsStringSync()) as YamlMap;
diff --git a/pkgs/markdown/tool/dartdoc_compare.dart b/pkgs/markdown/tool/dartdoc_compare.dart
index 7186d79..37be7fb 100644
--- a/pkgs/markdown/tool/dartdoc_compare.dart
+++ b/pkgs/markdown/tool/dartdoc_compare.dart
@@ -27,7 +27,6 @@
     )
     ..addFlag(
       _sdk,
-      defaultsTo: false,
       negatable: false,
       help: 'Is the package the SDK?',
     )
@@ -146,12 +145,14 @@
     // make modifiable copy
     dartdocPubspec = jsonDecode(jsonEncode(dartdocPubspec)) as Map;
 
+    final dependencies = dartdocPubspec['dependencies'] as Map;
+
     if (markdownRef == 'local') {
-      dartdocPubspec['dependencies']['markdown'] = {
+      dependencies['markdown'] = {
         'path': markdownPath,
       };
     } else {
-      dartdocPubspec['dependencies']['markdown'] = {
+      dependencies['markdown'] = {
         'git': {
           'url': 'git://github.com/dart-lang/markdown.git',
           'ref': markdownRef
diff --git a/pkgs/markdown/tool/expected_output.dart b/pkgs/markdown/tool/expected_output.dart
index 30d8fe9..b50adb0 100644
--- a/pkgs/markdown/tool/expected_output.dart
+++ b/pkgs/markdown/tool/expected_output.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:io';
-import 'dart:isolate';
 
 import 'package:path/path.dart' as p;
 
@@ -35,14 +34,14 @@
       description = 'line ${i + 1}: $description';
     }
 
-    var input = '';
+    final input = StringBuffer();
     while (!lines[i].startsWith('<<<')) {
-      input += '${lines[i++]}\n';
+      input.writeln(lines[i++]);
     }
 
-    var expectedOutput = '';
+    final expectedOutput = StringBuffer();
     while (++i < lines.length && !lines[i].startsWith('>>>')) {
-      expectedOutput += '${lines[i]}\n';
+      expectedOutput.writeln(lines[i]);
     }
 
     final dataCase = DataCase(
@@ -51,8 +50,8 @@
       front_matter: frontMatter.toString(),
       description: description,
       skip: skip,
-      input: input,
-      expectedOutput: expectedOutput,
+      input: input.toString(),
+      expectedOutput: expectedOutput.toString(),
     );
     yield dataCase;
   }
@@ -116,16 +115,12 @@
 ///   }
 /// }
 /// ```
-Stream<DataCase> dataCasesUnder({
+Iterable<DataCase> dataCasesUnder({
   required String testDirectory,
   String extension = 'unit',
   bool recursive = true,
-}) async* {
-  final packageUri = Uri.parse('package:markdown/markdown.dart');
-  final isolateUri = await Isolate.resolvePackageUri(packageUri);
-  final markdownLibRoot = p.dirname(isolateUri!.toFilePath());
-  final directory =
-      p.joinAll([p.dirname(markdownLibRoot), 'test', testDirectory]);
+}) sync* {
+  final directory = p.join(p.current, 'test', testDirectory);
   for (final dataCase in _dataCases(
     directory: directory,
     extension: extension,
diff --git a/pkgs/markdown/tool/stats.dart b/pkgs/markdown/tool/stats.dart
index d09b0df..6fe481c 100644
--- a/pkgs/markdown/tool/stats.dart
+++ b/pkgs/markdown/tool/stats.dart
@@ -27,30 +27,26 @@
     )
     ..addFlag(
       'raw',
-      defaultsTo: false,
       help: 'raw JSON format',
       negatable: false,
     )
     ..addFlag(
       'update-files',
-      defaultsTo: false,
       help: 'Update stats files in $toolDir',
       negatable: false,
     )
     ..addFlag(
       'verbose',
-      defaultsTo: false,
       help: 'Print details for failures and errors.',
       negatable: false,
     )
     ..addFlag(
       'verbose-loose',
-      defaultsTo: false,
       help: 'Print details for "loose" matches.',
       negatable: false,
     )
     ..addOption('flavor', allowed: _configs.map((c) => c.prefix))
-    ..addFlag('help', defaultsTo: false, negatable: false);
+    ..addFlag('help', negatable: false);
 
   ArgResults options;
 
@@ -153,7 +149,7 @@
 
       final nestedMap = scores.putIfAbsent(
         entry.key,
-        () => SplayTreeMap<int, CompareLevel>(),
+        SplayTreeMap<int, CompareLevel>.new,
       );
       nestedMap[e.example] = result.compareLevel;
     }
@@ -223,6 +219,7 @@
   const encoder = JsonEncoder.withIndent(' ', _convert);
   try {
     sink.writeln(encoder.convert(scores));
+    // ignore: avoid_catching_errors
   } on JsonUnsupportedObjectError catch (e) {
     stderr.writeln(e.cause);
     stderr.writeln(e.unsupportedObject.runtimeType);
diff --git a/pkgs/markdown/tool/update_blns.dart b/pkgs/markdown/tool/update_blns.dart
index 1f50e36..e2b5c62 100644
--- a/pkgs/markdown/tool/update_blns.dart
+++ b/pkgs/markdown/tool/update_blns.dart
@@ -1,23 +1,14 @@
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
+import 'update_shared.dart';
+
 const _blnsJsonRawUrl =
     'https://github.com/minimaxir/big-list-of-naughty-strings/raw/master/blns.json';
 const _blnsFilePath = 'test/blns.dart';
 
 Future<void> main() async {
-  final client = HttpClient();
-  List<String> json;
-  try {
-    final request = await client.getUrl(Uri.parse(_blnsJsonRawUrl));
-    final response = await request.close();
-    final source =
-        await response.cast<List<int>>().transform(utf8.decoder).join('');
-    json = (jsonDecode(source) as List).cast<String>();
-  } finally {
-    client.close();
-  }
+  final json = (await downloadJson(_blnsJsonRawUrl) as List).cast<String>();
   final blnsContent = StringBuffer('''
 // GENERATED FILE. DO NOT EDIT.
 //
diff --git a/pkgs/markdown/tool/update_case_folding.dart b/pkgs/markdown/tool/update_case_folding.dart
index 02cbd16..e53f70b 100644
--- a/pkgs/markdown/tool/update_case_folding.dart
+++ b/pkgs/markdown/tool/update_case_folding.dart
@@ -5,18 +5,17 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:path/path.dart' as p;
+
 // Generates and updates unicode case folding map.
 // Here only extract status C + F capital letters.
 void main() {
-  final root = File(Platform.script.path).parent.parent.path;
   // Downloaded from http://www.unicode.org/Public/14.0.0/ucd/CaseFolding.txt
-  final file = File('$root/tool/case_folding.txt');
-  final text = file.readAsStringSync();
-  final lines = LineSplitter().convert(text);
+  final file = File('${p.current}/tool/case_folding.txt');
 
   final result = <String, String>{};
 
-  for (final line in lines) {
+  for (final line in file.readAsLinesSync()) {
     if (line.startsWith('#') ||
         line.trim().isEmpty ||
         !line.contains('CAPITAL LETTER')) {
@@ -37,8 +36,8 @@
     result[key] = value;
   }
 
-  final outputPath = '$root/lib/src/assets/case_folding.dart';
-  final stringMap = JsonEncoder.withIndent('  ').convert(result);
+  final outputPath = '${p.current}/lib/src/assets/case_folding.dart';
+  final stringMap = const JsonEncoder.withIndent('  ').convert(result);
   final output = '''
 // Generated file. do not edit.
 //
diff --git a/pkgs/markdown/tool/update_emojis.dart b/pkgs/markdown/tool/update_emojis.dart
index 396be33..dad59c7 100644
--- a/pkgs/markdown/tool/update_emojis.dart
+++ b/pkgs/markdown/tool/update_emojis.dart
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
+import 'update_shared.dart';
+
 // update_github_emojis.dart now generates the emoji list using the GitHub API
 // to retrieve the emoji list.  It uses this emoji source as a source to keep
 // binary compatibility with the Unicode sequences for each emoji found here.
@@ -14,14 +15,11 @@
 const _emojisFilePath = 'lib/src/legacy_emojis.dart';
 
 Future<void> main() async {
-  final client = HttpClient();
-  final request = await client.getUrl(Uri.parse(_emojisJsonRawUrl));
-  final response = await request.close();
-  final json = jsonDecode(
-          await response.cast<List<int>>().transform(utf8.decoder).join(''))
-      .map((String alias, dynamic info) =>
-          MapEntry(alias, info.cast<String, dynamic>()))
-      .cast<String, Map<String, dynamic>>();
+  final json =
+      (await downloadJson(_emojisJsonRawUrl) as Map<String, dynamic>).map(
+    (String alias, dynamic info) =>
+        MapEntry(alias, info as Map<String, dynamic>),
+  );
   final emojisContent = StringBuffer('''
 // GENERATED FILE. DO NOT EDIT.
 //
@@ -35,7 +33,7 @@
   final ignored = <String>[];
   // Dump in sorted order now to facilitate comparison with new GitHub emoji.
   final sortedKeys = json.keys.toList()..sort();
-  for (final String alias in sortedKeys) {
+  for (final alias in sortedKeys) {
     final info = json[alias] as Map<String, dynamic>;
     if (info['char'] != null) {
       emojisContent.writeln("  '$alias': '${info['char']}',");
@@ -50,5 +48,4 @@
       'emoji recognized by the markdown package, execute `update_github_emojis.dart`.\n');
   print('Wrote data to $_emojisFilePath for $emojiCount emoji, '
       'ignoring ${ignored.length}: ${ignored.join(', ')}.');
-  exit(0);
 }
diff --git a/pkgs/markdown/tool/update_github_emojis.dart b/pkgs/markdown/tool/update_github_emojis.dart
index 5fbf60c..d5af661 100644
--- a/pkgs/markdown/tool/update_github_emojis.dart
+++ b/pkgs/markdown/tool/update_github_emojis.dart
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
 import 'package:args/args.dart';
-
 import 'package:markdown/src/legacy_emojis.dart' as legacy;
 
+import 'update_shared.dart';
+
 /// Regular expression to match GitHub emoji API output filenames.
 RegExp gitHubEmojiApiPattern =
     RegExp(r'^[\ \t]+"(.*?)".*unicode\/([A-Fa-f0-9\-]+)\.png');
@@ -140,7 +140,7 @@
   const zeroWidthJoiner = 0x200D;
 
   try {
-    final String? rawHexList = gitHubEmojiUnicodeFromFilenamePattern
+    final rawHexList = gitHubEmojiUnicodeFromFilenamePattern
         .firstMatch(emojiFilename)
         ?.group(1);
     if (rawHexList == null) {
@@ -183,7 +183,7 @@
   } catch (e) {
     print(
         'Invalid/Non-Conformant emoji filename encountered "$emojiFilename"!');
-    return (errorSpecialReplacement);
+    return errorSpecialReplacement;
   }
 }
 
@@ -215,22 +215,24 @@
   try {
     results = parser.parse(args);
   } catch (e) {
+    print(e);
     printUsage(parser);
-    exit(0);
+    return;
   }
 
   if (results['help'] as bool) {
     printUsage(parser);
-    exit(0);
+    return;
   }
 
   var totalEmojiWithDifferentUnicodeSequences = 0;
   final useLegacyUnicodeSequences = !(results['useGitHubUnicodes'] as bool);
   final visualizeUnicodeDiffs = results['visualizeDifferentUnicodes'] as bool;
-  final dumpMarkdownShortCodes =
-      (results['dumpMarkdownShortCodes'].toLowerCase() == 'plain');
-  final dumpMarkdownToolTipShortCodes =
-      (results['dumpMarkdownShortCodes'].toLowerCase() == 'tooltip');
+
+  final shortCodes =
+      (results['dumpMarkdownShortCodes'] as String).toLowerCase();
+  final dumpMarkdownShortCodes = shortCodes == 'plain';
+  final dumpMarkdownToolTipShortCodes = shortCodes == 'tooltip';
 
   if (!useLegacyUnicodeSequences) {
     // Issue warning of the implications of using full GitHub emjoi Unicode sequences.
@@ -240,14 +242,13 @@
     print(
         'The following emoji have different Unicode sequences from those of legacy versions:');
   }
-  final client = HttpClient();
-  final request = await client.getUrl(Uri.parse(_emojisJsonRawUrl));
-  final response = await request.close();
-  final shortcodeToEmoji = jsonDecode(
-          await response.cast<List<int>>().transform(utf8.decoder).join(''))
-      .map((String alias, dynamic filename) => MapEntry(
-          alias, parseGitHubFilenameIntoUnicodeString(filename as String)))
-      .cast<String, String>() as Map<String, String>;
+  final shortcodeToEmoji =
+      (await downloadJson(_emojisJsonRawUrl) as Map<String, dynamic>).map(
+    (String alias, dynamic filename) => MapEntry(
+      alias,
+      parseGitHubFilenameIntoUnicodeString(filename as String),
+    ),
+  );
 
   // Now before we proceed we need to 'mix in' any legacy emoji alias shortcodes that
   // are missing from the GitHub emoji list.
@@ -271,8 +272,8 @@
   final errored = <String>[];
   // Dump in sorted order now to facilitate comparison with new GitHub emoji.
   final sortedKeys = shortcodeToEmoji.keys.toList()..sort();
-  for (final String shortCodeAlias in sortedKeys) {
-    String emojiUnicode = shortcodeToEmoji[shortCodeAlias]!;
+  for (final shortCodeAlias in sortedKeys) {
+    var emojiUnicode = shortcodeToEmoji[shortCodeAlias]!;
     if (useLegacyUnicodeSequences &&
         legacyEmojis.containsKey(shortCodeAlias) &&
         shortCodeAlias != 'cricket' &&
@@ -312,14 +313,13 @@
     // is being captured, so we exit now to exclude the summary
     // report from being included in the emoji markdown we have
     // been outputing.
-    exit(0);
+    return;
   }
 
   print('''Wrote data to $_emojisFilePath for $emojiCount emoji,
 $totalEmojiWithDifferentUnicodeSequences emoji's Unicode sequences differ from legacy versions${!visualizeUnicodeDiffs ? " (run with -v flag to visualize)" : ""},
 ignoring ${ignored.length}: ${ignored.join(', ')},
 errored: ${errored.length} ${errored.join(', ')}.''');
-  exit(0);
 }
 
 void printUsage(ArgParser parser) {
diff --git a/pkgs/markdown/tool/update_shared.dart b/pkgs/markdown/tool/update_shared.dart
new file mode 100644
index 0000000..6992cd5
--- /dev/null
+++ b/pkgs/markdown/tool/update_shared.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, 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 'dart:io';
+
+Future<Object?> downloadJson(String uri) async {
+  final client = HttpClient();
+  try {
+    final request = await client.getUrl(Uri.parse(uri));
+    final response = await request.close();
+
+    return response
+        .transform(utf8.decoder)
+        .transform(const JsonDecoder())
+        .single;
+  } finally {
+    client.close();
+  }
+}