Add annotations to direct the null-safety migration tool.

Change-Id: Id025c8022aaae417b70ef1cd32e0fb8ec4af3e6b
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/223940
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/builder/bin/update_results_database.dart b/builder/bin/update_results_database.dart
index e94d20a..41a14f0 100644
--- a/builder/bin/update_results_database.dart
+++ b/builder/bin/update_results_database.dart
@@ -17,7 +17,7 @@
 
 Future<List<Map<String, dynamic>>> readChangedResults(File resultsFile) async {
   final lines = (await resultsFile.readAsLines())
-      .map((line) => jsonDecode(line) as Map<String, dynamic>);
+      .map((line) => jsonDecode(line) /*!*/ as Map<String, dynamic>);
   if (lines.isEmpty) {
     print('Empty input results.json file');
     exit(1);
diff --git a/builder/lib/src/builder.dart b/builder/lib/src/builder.dart
index 92b8307..91223d2 100644
--- a/builder/lib/src/builder.dart
+++ b/builder/lib/src/builder.dart
@@ -15,14 +15,14 @@
 /// [FirestoreService] object.
 /// Tryjob builds are represented by the class [Tryjob] instead.
 class Build {
-  final FirestoreService firestore;
-  final CommitsCache commitsCache;
-  final BuildInfo info;
+  final FirestoreService /*!*/ firestore;
+  final CommitsCache /*!*/ commitsCache;
+  final BuildInfo /*!*/ info;
   final TestNameLock testNameLock = TestNameLock();
-  int startIndex;
-  int endIndex;
-  Commit endCommit;
-  List<Commit> commits;
+  /*late final*/ int /*!*/ startIndex;
+  /*late*/ int /*!*/ endIndex;
+  /*late*/ Commit /*!*/ endCommit;
+  List<Commit /*!*/ > commits;
   Map<String, int> tryApprovals = {};
   List<RevertedChanges> allRevertedChanges = [];
 
@@ -36,7 +36,7 @@
 
   void log(String string) => firestore.log(string);
 
-  Future<void> process(List<Map<String, dynamic>> changes) async {
+  Future<void> process(List<Map<String, dynamic> /*!*/ > changes) async {
     log('store build commits info');
     await storeBuildCommitsInfo();
     log('update build info');
@@ -47,8 +47,9 @@
       // TODO(karlklose): add a flag to overwrite builder results.
       return;
     }
-    final configurations =
-        changes.map((change) => change['configuration'] as String).toSet();
+    final configurations = changes
+        .map((change) => change['configuration'] as String /*!*/)
+        .toSet();
     await update(configurations);
     log('storing ${changes.length} change(s)');
     await Pool(30).forEach(changes, guardedStoreChange).drain();
@@ -72,7 +73,7 @@
     log(report.join('\n'));
   }
 
-  Future<void> update(Iterable<String> configurations) async {
+  Future<void> update(Iterable<String /*!*/ > configurations) async {
     await storeConfigurationsInfo(configurations);
   }
 
@@ -81,17 +82,18 @@
   /// Saves the commit indices of the start and end of the blamelist.
   Future<void> storeBuildCommitsInfo() async {
     // Get indices of change.  Range includes startIndex and endIndex.
-    endCommit = await commitsCache.getCommit(info.commitRef);
-    if (endCommit == null) {
+    final commit = await commitsCache.getCommit(info.commitRef);
+    if (commit == null) {
       throw 'Result received with unknown commit hash ${info.commitRef}';
     }
+    endCommit = commit;
     endIndex = endCommit.index;
     // If this is a new builder, use the current commit as a trivial blamelist.
     if (info.previousCommitHash == null) {
       startIndex = endIndex;
     } else {
       final startCommit = await commitsCache.getCommit(info.previousCommitHash);
-      startIndex = startCommit.index + 1;
+      startIndex = startCommit /*!*/ .index + 1;
       if (startIndex > endIndex) {
         throw ArgumentError('Results received with empty blamelist\n'
             'previous commit: ${info.previousCommitHash}\n'
@@ -109,7 +111,7 @@
       endCommit
     ];
     for (final commit in commits) {
-      final index = commit.index;
+      final index = commit /*!*/ .index;
       final review = commit.review;
       final reverted = commit.revertOf;
       if (review != null) {
@@ -139,7 +141,7 @@
     await fetchReviewsAndReverts();
     transformChange(change);
     final failure = isFailure(change);
-    bool approved;
+    bool /*!*/ approved;
     var result = await firestore.findResult(change, startIndex, endIndex);
     var activeResults = await firestore.findActiveResults(
         change['name'], change['configuration']);
@@ -189,7 +191,7 @@
 
 Map<String, dynamic> constructResult(
     Map<String, dynamic> change, int startIndex, int endIndex,
-    {bool approved, int landedReviewIndex, bool failure}) {
+    {bool /*!*/ approved, int landedReviewIndex, bool failure}) {
   return {
     fName: change[fName],
     fResult: change[fResult],
@@ -198,9 +200,9 @@
     fBlamelistStartIndex: startIndex,
     fBlamelistEndIndex: endIndex,
     if (startIndex != endIndex && approved) fPinnedIndex: landedReviewIndex,
-    fConfigurations: <String>[change['configuration']],
+    fConfigurations: <String /*!*/ >[change['configuration']],
     fApproved: approved,
     if (failure) fActive: true,
-    if (failure) fActiveConfigurations: <String>[change['configuration']]
+    if (failure) fActiveConfigurations: <String /*!*/ >[change['configuration']]
   };
 }
diff --git a/builder/lib/src/commits_cache.dart b/builder/lib/src/commits_cache.dart
index 67d5362..7fe9043 100644
--- a/builder/lib/src/commits_cache.dart
+++ b/builder/lib/src/commits_cache.dart
@@ -16,39 +16,47 @@
 /// The class fetches commits from Firestore if they are present,
 /// and fetches them from gitiles if not, and saves them to Firestore.
 class CommitsCache {
-  FirestoreService firestore;
-  final http.Client httpClient;
-  Map<String, Commit> byHash = {};
+  FirestoreService /*!*/ firestore;
+  final http.Client /*!*/ httpClient;
+  Map<String /*!*/, Commit> byHash = {};
   Map<int, Commit> byIndex = {};
   int startIndex;
   int endIndex;
 
   CommitsCache(this.firestore, this.httpClient);
 
-  Future<Commit> getCommit(String hash) async {
-    return byHash[hash] ??
-        await _fetchByHash(hash) ??
-        await _getNewCommits() ??
-        await _fetchByHash(hash) ??
-        _reportError('getCommit($hash)');
+  Future<Commit /*!*/ > getCommit(String /*!*/ hash) async {
+    var commit = byHash[hash] ?? await _fetchByHash(hash);
+    if (commit == null) {
+      await _getNewCommits();
+      commit = await _fetchByHash(hash);
+    }
+    if (commit == null) {
+      throw _makeError('getCommit($hash)');
+    }
+    return commit;
   }
 
-  Future<Commit> getCommitByIndex(int index) async {
-    return byIndex[index] ??
-        await _fetchByIndex(index) ??
-        await _getNewCommits() ??
-        await _fetchByIndex(index) ??
-        _reportError('getCommitByIndex($index)');
+  Future<Commit /*!*/ > getCommitByIndex(int /*!*/ index) async {
+    var commit = byIndex[index] ?? await _fetchByIndex(index);
+    if (commit == null) {
+      await _getNewCommits();
+      commit = await _fetchByIndex(index);
+    }
+    if (commit == null) {
+      throw _makeError('getCommitByIndex($index)');
+    }
+    return commit;
   }
 
-  Commit _reportError(String message) {
+  String _makeError(String message) {
     final error = 'Failed to fetch commit: $message\n'
         'Commit cache holds:\n'
-        '  $startIndex: ${byIndex[startIndex]}\n'
+        '  $startIndex: ${byIndex[startIndex ?? -1]}\n'
         '  ...\n'
-        '  $endIndex: ${byIndex[endIndex]}';
+        '  $endIndex: ${byIndex[endIndex ?? -1]}';
     print(error);
-    throw error;
+    return error;
   }
 
   /// Add a commit to the cache. The cache must be empty, or the commit
@@ -68,7 +76,7 @@
     byIndex[index] = commit;
   }
 
-  Future<Commit> _fetchByHash(String hash) async {
+  Future<Commit> _fetchByHash(String /*!*/ hash) async {
     final commit = await firestore.getCommit(hash);
     if (commit == null) return null;
     final index = commit.index;
@@ -96,14 +104,14 @@
     return commit;
   }
 
-  Future<Commit> _fetchByIndex(int index) => firestore
+  Future<Commit> _fetchByIndex(int /*!*/ index) => firestore
       .getCommitByIndex(index)
       .then((commit) => _fetchByHash(commit.hash));
 
   /// This function is idempotent, so every call of it should write the
   /// same info to new Firestore documents. It is safe to call multiple
   /// times simultaneously. Intentionally returns null, not void.
-  Future<Commit> _getNewCommits() async {
+  Future<void> _getNewCommits() async {
     const prefix = ")]}'\n";
     final lastCommit = await firestore.getLastCommit();
     final lastHash = lastCommit.hash;
@@ -120,20 +128,19 @@
       throw Exception('Gerrit response missing prefix $prefix: $protectedJson.'
           'Requested URL: $url');
     }
-    final commits = jsonDecode(protectedJson.substring(prefix.length))['log']
-        as List<dynamic>;
+    final commits = List.castFrom<dynamic, Map<String, dynamic>>(
+        jsonDecode(protectedJson.substring(prefix.length))['log']);
     if (commits.isEmpty) {
       print('Found no new commits between $lastHash and $branch');
-      return null;
     }
     print('Fetched new commits from Gerrit (gitiles): $commits');
-    final first = commits.last as Map<String, dynamic>;
+    final first = commits.last;
     if (first['parents'].first != lastHash) {
       throw 'First new commit ${first['commit']} is not'
           ' a child of last known commit $lastHash when fetching new commits';
     }
     var index = lastIndex + 1;
-    for (Map<String, dynamic> commit in commits.reversed) {
+    for (final commit in commits.reversed) {
       final review = _review(commit);
       var reverted = _revert(commit);
       var relanded = _reland(commit);
@@ -161,7 +168,6 @@
       }
       ++index;
     }
-    return null;
   }
 
   /// This function is idempotent and may be called multiple times
@@ -180,11 +186,10 @@
   TestingCommitsCache(firestore, httpClient) : super(firestore, httpClient);
 
   @override
-  Future<Commit> _getNewCommits() async {
+  Future<void> _getNewCommits() async {
     if ((await firestore.isStaging())) {
       return super._getNewCommits();
     }
-    return null;
   }
 }
 
diff --git a/builder/lib/src/firestore.dart b/builder/lib/src/firestore.dart
index 91f6435..13d6824 100644
--- a/builder/lib/src/firestore.dart
+++ b/builder/lib/src/firestore.dart
@@ -16,22 +16,22 @@
 
 class Commit {
   final SafeDocument document;
-  final String hash;
+  final String hash /*!*/;
   Commit(this.hash, this.document);
   Commit.fromJson(this.hash, Map<String, dynamic> data)
       : document = SafeDocument(Document(fields: taggedMap(data), name: ''));
-  int get index => document.getInt('index');
+  int /*!*/ get index => document.getInt('index');
   String get revertOf => document.getString(fRevertOf);
-  bool get isRevert => document.fields.containsKey(fRevertOf);
+  bool /*!*/ get isRevert => document.fields.containsKey(fRevertOf);
   int get review => document.getInt(fReview);
 
   Map<String, Object> toJson() => untagMap(document.fields);
 }
 
 class FirestoreService {
-  final String project;
-  final FirestoreApi firestore;
-  final http.Client client;
+  final String /*!*/ project;
+  final FirestoreApi /*!*/ firestore;
+  final http.Client /*!*/ client;
   int documentsFetched = 0;
   int documentsWritten = 0;
 
@@ -50,7 +50,7 @@
   }
 
   Future<List<SafeDocument>> query(
-      {String from,
+      {String /*!*/ from,
       Filter where,
       Order orderBy,
       int limit,
@@ -71,14 +71,26 @@
     return runQuery(query, parent: parent);
   }
 
-  Future<Document> getDocument(String path,
-      {bool throwOnNotFound = true}) async {
+  Future<Document /*!*/ > getDocument(String path) async {
+    try {
+      final document = await firestore.projects.databases.documents.get(path);
+      documentsFetched++;
+      return document;
+    } on DetailedApiRequestError {
+      log("Failed to get document '$path'");
+      rethrow;
+    }
+  }
+
+  Future<Document /*?*/ > getDocumentOrNull(
+    String path,
+  ) async {
     try {
       final document = await firestore.projects.databases.documents.get(path);
       documentsFetched++;
       return document;
     } on DetailedApiRequestError catch (e) {
-      if (!throwOnNotFound && e.status == 404) {
+      if (e.status == 404) {
         return null;
       } else {
         log("Failed to get document '$path'");
@@ -116,7 +128,7 @@
 
   Future<bool> isStaging() => Future.value(project == 'dart-ci-staging');
 
-  Future<bool> hasPatchset(String review, String patchset) {
+  Future<bool> hasPatchset(String /*!*/ review, String /*!*/ patchset) {
     return documentExists('$documents/reviews/$review/patchsets/$patchset');
   }
 
@@ -126,8 +138,7 @@
   }
 
   Future<Commit> getCommit(String hash) async {
-    final document =
-        await getDocument('$documents/commits/$hash', throwOnNotFound: false);
+    final document = await getDocumentOrNull('$documents/commits/$hash');
     return document != null ? Commit(hash, SafeDocument(document)) : null;
   }
 
@@ -143,7 +154,7 @@
     return _commit(lastCommit.first);
   }
 
-  Future<void> addCommit(String id, Map<String, dynamic> data) async {
+  Future<void> addCommit(String /*!*/ id, Map<String, dynamic> data) async {
     try {
       final document = Document()..fields = taggedMap(data);
       await firestore.projects.databases.documents
@@ -161,8 +172,8 @@
 
   Future<void> updateConfiguration(String configuration, String builder) async {
     documentsWritten++;
-    final record = await getDocument('$documents/configurations/$configuration',
-        throwOnNotFound: false);
+    final record =
+        await getDocumentOrNull('$documents/configurations/$configuration');
     if (record == null) {
       final newRecord = Document()..fields = taggedMap({'builder': builder});
       await firestore.projects.databases.documents.createDocument(
@@ -186,8 +197,7 @@
   /// for this build.
   Future<bool> updateBuildInfo(
       String builder, int buildNumber, int index) async {
-    final record = await getDocument('$documents/builds/$builder:$index',
-        throwOnNotFound: false);
+    final record = await getDocumentOrNull('$documents/builds/$builder:$index');
     if (record == null) {
       final newRecord = Document()
         ..fields = taggedMap(
@@ -230,15 +240,14 @@
     await firestore.projects.databases.documents
         .createDocument(newRecord, documents, 'try_builds');
     documentsWritten++;
-    return true;
   }
 
   Future<String> findResult(
       Map<String, dynamic> change, int startIndex, int endIndex) async {
-    final name = change['name'] as String;
-    final result = change['result'] as String;
-    final previousResult = change['previous_result'] as String;
-    final expected = change['expected'] as String;
+    final name = change['name'] as String /*!*/;
+    final result = change['result'] as String /*!*/;
+    final previousResult = change['previous_result'] as String /*!*/;
+    final expected = change['expected'] as String /*!*/;
     final snapshot = await query(
         from: 'results',
         orderBy: orderBy('blamelist_end_index', false),
@@ -261,7 +270,7 @@
         ?.name;
   }
 
-  Future<Document> storeResult(Map<String, dynamic> result) async {
+  Future<Document /*!*/ > storeResult(Map<String, dynamic> result) async {
     final document = Document()..fields = taggedMap(result);
     final createdDocument = await firestore.projects.databases.documents
         .createDocument(document, documents, 'results');
@@ -270,13 +279,13 @@
     return createdDocument;
   }
 
-  Future<bool> updateResult(
+  Future<bool /*!*/ > updateResult(
       String result, String configuration, int startIndex, int endIndex,
-      {bool failure}) async {
+      {/*required*/ bool failure}) async {
     bool approved;
     await retryCommit(() async {
       final document = await getDocument(result);
-      final data = SafeDocument(document);
+      final data = SafeDocument(document /*!*/);
       // Allow missing 'approved' field during transition period.
       approved = data.getBool('approved') ?? false;
       // Add the new configuration and narrow the blamelist.
@@ -334,7 +343,7 @@
 
   /// Returns all results which are either pinned to or have a range that is
   /// this single index. // TODO: rename this function
-  Future<List<Map<String, Value>>> findRevertedChanges(int index) async {
+  Future<List<Map<String, Value> /*!*/ >> findRevertedChanges(int index) async {
     final pinnedResults =
         await query(from: 'results', where: fieldEquals('pinned_index', index));
     final results = pinnedResults.map((response) => response.fields).toList();
@@ -351,11 +360,11 @@
 
   Future<bool> storeTryChange(
       Map<String, dynamic> change, int review, int patchset) async {
-    final name = change['name'] as String;
-    final result = change['result'] as String;
-    final expected = change['expected'] as String;
-    final previousResult = change['previous_result'] as String;
-    final configuration = change['configuration'] as String;
+    final name = change['name'] as String /*!*/;
+    final result = change['result'] as String /*!*/;
+    final expected = change['expected'] as String /*!*/;
+    final previousResult = change['previous_result'] as String /*!*/;
+    final configuration = change['configuration'] as String /*!*/;
 
     // Find an existing result record for this test on this patchset.
     final responses = await query(
@@ -400,7 +409,7 @@
           'expected': expected,
           'review': review,
           'patchset': patchset,
-          'configurations': <String>[configuration],
+          'configurations': <String /*!*/ >[configuration],
           'approved': approved
         });
       await firestore.projects.databases.documents
@@ -449,8 +458,9 @@
     assert(configurations.contains(configuration));
     await removeArrayEntry(
         activeResult, 'active_configurations', taggedValue(configuration));
-    activeResult = SafeDocument(await getDocument(activeResult.name));
-    if (activeResult.getList('active_configurations').isEmpty) {
+    final document = await getDocument(activeResult.name);
+    activeResult = SafeDocument(document /*!*/);
+    if (activeResult.getList('active_configurations')?.isEmpty == true) {
       activeResult.fields.remove('active_configurations');
       activeResult.fields.remove('active');
       final write = Write()
@@ -475,8 +485,8 @@
     ]);
   }
 
-  Future<List<SafeDocument>> findActiveResults(
-      String name, String configuration) async {
+  Future<List<SafeDocument /*!*/ >> findActiveResults(
+      String /*!*/ name, String /*!*/ configuration) async {
     final results = await query(
         from: 'results',
         where: compositeFilter([
@@ -504,12 +514,12 @@
         .createDocument(document, documents, 'reviews', documentId: review);
   }
 
-  Future<void> deleteDocument(String name) async {
+  Future<void> deleteDocument(String /*!*/ name) async {
     return _executeWrite([Write()..delete = name]);
   }
 
   Future<bool> documentExists(String name) async {
-    return (await getDocument(name, throwOnNotFound: false) != null);
+    return (await getDocumentOrNull(name) != null);
   }
 
   Future _executeWrite(List<Write> writes) async {
@@ -522,7 +532,8 @@
     return firestore.projects.databases.documents.batchWrite(request, database);
   }
 
-  Future<void> updateFields(Document document, List<String> fields) async {
+  Future<void> updateFields(
+      Document /*!*/ document, List<String> fields) async {
     await _executeWrite([
       Write()
         ..update = document
@@ -530,8 +541,13 @@
     ]);
   }
 
-  Future<void> storePatchset(String review, int patchset, String kind,
-      String description, int patchsetGroup, int number) async {
+  Future<void> storePatchset(
+      String /*!*/ review,
+      int /*!*/ patchset,
+      String /*!*/ kind,
+      String /*!*/ description,
+      int /*!*/ patchsetGroup,
+      int /*!*/ number) async {
     final document = Document()
       ..name = '$documents/reviews/$review/patchsets/$patchset'
       ..fields = taggedMap({
@@ -549,22 +565,21 @@
   /// Returns true if a review record in the database has a landed_index field,
   /// or if there is no record for the review in the database.  Reviews with no
   /// test failures have no record, and don't need to be linked when landing.
-  Future<bool> reviewIsLanded(int review) async {
-    final document =
-        await getDocument('$documents/reviews/$review', throwOnNotFound: false);
+  Future<bool> reviewIsLanded(int /*!*/ review) async {
+    final document = await getDocumentOrNull('$documents/reviews/$review');
     if (document == null) {
       return true;
     }
     return document.fields.containsKey('landed_index');
   }
 
-  Future<void> linkReviewToCommit(int review, int index) async {
+  Future<void> linkReviewToCommit(int /*!*/ review, int index) async {
     final document = await getDocument('$documents/reviews/$review');
     document.fields['landed_index'] = taggedValue(index);
     await updateFields(document, ['landed_index']);
   }
 
-  Future<void> linkCommentsToCommit(int review, int index) async {
+  Future<void> linkCommentsToCommit(int /*!*/ review, int index) async {
     final comments =
         await query(from: 'comments', where: fieldEquals('review', review));
     if (comments.isEmpty) return;
@@ -580,7 +595,7 @@
     await _executeWrite(writes);
   }
 
-  Future<List<SafeDocument>> tryApprovals(int review) async {
+  Future<List<SafeDocument /*!*/ >> tryApprovals(int review) async {
     final patchsets = await query(
         from: 'patchsets',
         parent: 'reviews/$review',
@@ -599,7 +614,7 @@
         ]));
   }
 
-  Future<List<SafeDocument>> tryResults(
+  Future<List<SafeDocument /*!*/ >> tryResults(
       int review, String configuration) async {
     final patchsets = await query(
         from: 'patchsets',
@@ -626,10 +641,11 @@
       String builder, int index, bool success) async {
     final path = '$documents/builds/$builder:$index';
     final document = await getDocument(path);
-    await _completeBuilderRecord(document, success);
+    await _completeBuilderRecord(document /*!*/, success);
   }
 
-  Future<void> _completeBuilderRecord(Document document, bool success) async {
+  Future<void> _completeBuilderRecord(
+      Document /*!*/ document, bool success) async {
     await retryCommit(() async {
       document.fields['success'] = taggedValue(success);
       final write = Write()
diff --git a/builder/lib/src/firestore_helpers.dart b/builder/lib/src/firestore_helpers.dart
index b4bea17..aa4a92b 100644
--- a/builder/lib/src/firestore_helpers.dart
+++ b/builder/lib/src/firestore_helpers.dart
@@ -138,9 +138,3 @@
       ..filters = filters
       ..op = 'AND');
 }
-
-class DataWrapper {
-  final Map<String, Value> fields;
-  DataWrapper(Document document) : fields = document.fields;
-  DataWrapper.fields(this.fields);
-}
diff --git a/builder/lib/src/gerrit_change.dart b/builder/lib/src/gerrit_change.dart
index 1c1b9ea..0cd189e 100644
--- a/builder/lib/src/gerrit_change.dart
+++ b/builder/lib/src/gerrit_change.dart
@@ -19,15 +19,14 @@
   };
   static const prefix = ")]}'\n";
 
-  http.BaseClient httpClient;
-  FirestoreService firestore;
-  String review;
-  String patchset;
+  final http.Client httpClient;
+  final FirestoreService firestore;
+  final String /*!*/ review;
+  final String patchset;
 
-  GerritInfo(int review, int patchset, this.firestore, this.httpClient) {
-    this.review = review.toString();
-    this.patchset = patchset.toString();
-  }
+  GerritInfo(int review, int patchset, this.firestore, this.httpClient)
+      : review = review.toString(),
+        patchset = patchset.toString();
 
   /// Fetches the owner, changeId, message, and date of a Gerrit change and
   /// stores them in the databases.
@@ -52,9 +51,9 @@
     // Add the patchset information to the patchsets subcollection.
     final revisions = reviewInfo['revisions'].values.toList()
       ..sort((a, b) => (a['_number'] as int).compareTo(b['_number']));
-    int patchsetGroupFirst;
+    int patchsetGroupFirst = 1;
     for (Map<String, dynamic> revision in revisions) {
-      int number = revision['_number'];
+      int number = revision['_number'] /*!*/;
       if (!trivialKinds.contains(revision['kind'])) {
         patchsetGroupFirst = number;
       }
diff --git a/builder/lib/src/result.dart b/builder/lib/src/result.dart
index acc4367..20a95a2 100644
--- a/builder/lib/src/result.dart
+++ b/builder/lib/src/result.dart
@@ -10,11 +10,11 @@
 import 'package:googleapis/firestore/v1.dart' show Value;
 
 class ResultRecord {
-  final Map<String, Value> fields;
+  final Map<String, Value> /*!*/ fields;
 
   ResultRecord(this.fields);
 
-  bool get approved => fields['approved'].booleanValue;
+  bool /*!*/ get approved => fields['approved'].booleanValue;
 
   @override
   String toString() => jsonEncode(fields);
@@ -23,7 +23,7 @@
     return int.parse(fields['blamelist_end_index'].integerValue);
   }
 
-  bool containsActiveConfiguration(String configuration) {
+  bool containsActiveConfiguration(String /*!*/ configuration) {
     for (final value in fields['active_configurations'].arrayValue.values) {
       if (value.stringValue != null && value.stringValue == configuration) {
         return true;
@@ -50,7 +50,7 @@
 const fConfigurations = 'configurations';
 const fActiveConfigurations = 'active_configurations';
 
-bool isChangedResult(Map<String, dynamic> change) =>
+bool isChangedResult(Map<String, dynamic> /*!*/ change) =>
     change[fChanged] && (!change[fFlaky] || !change[fPreviousFlaky]);
 
 /// Whether the change will be marked as an active failure.
@@ -96,9 +96,9 @@
 class BuildInfo {
   static final commitRefRegExp = RegExp(r'refs/changes/(\d*)/(\d*)');
 
-  final String builderName;
+  final String /*!*/ builderName;
   final int buildNumber;
-  final String commitRef;
+  final String /*!*/ commitRef;
   final String previousCommitHash;
 
   BuildInfo(Map<String, dynamic> result)
@@ -126,11 +126,11 @@
 }
 
 class TestNameLock {
-  final locks = <String, Future<void>>{};
+  final locks = <String /*!*/, Future<void>>{};
 
   Future<void> guardedCall(Future<void> Function(Map<String, dynamic> change) f,
-      Map<String, dynamic> change) async {
-    final name = change[fName];
+      Map<String, dynamic> /*!*/ change) async {
+    final name = change[fName] /*!*/;
     while (locks.containsKey(name)) {
       await locks[name];
     }
diff --git a/builder/lib/src/reverted_changes.dart b/builder/lib/src/reverted_changes.dart
index d7bf314..e16304b 100644
--- a/builder/lib/src/reverted_changes.dart
+++ b/builder/lib/src/reverted_changes.dart
@@ -23,8 +23,8 @@
 class RevertedChanges {
   final int index;
   final int revertIndex;
-  final List<Map<String, Value>> changes;
-  final Map<String, List<Map<String, Value>>> changesForTest;
+  final List<Map<String, Value> /*!*/ > changes;
+  final Map<String /*!*/, List<Map<String, Value> /*!*/ >> changesForTest;
 
   RevertedChanges(
       this.index, this.revertIndex, this.changes, this.changesForTest);
diff --git a/builder/lib/src/tryjob.dart b/builder/lib/src/tryjob.dart
index c07ef13..31e371b 100644
--- a/builder/lib/src/tryjob.dart
+++ b/builder/lib/src/tryjob.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:collection/collection.dart';
-import 'package:http/http.dart' as http show BaseClient;
+import 'package:http/http.dart' as http show Client;
 import 'package:pool/pool.dart';
 
 import 'commits_cache.dart';
@@ -57,17 +57,17 @@
 }
 
 class Tryjob {
-  final http.BaseClient httpClient;
-  final FirestoreService firestore;
-  final CommitsCache commits;
+  final http.Client /*!*/ httpClient;
+  final FirestoreService /*!*/ firestore;
+  final CommitsCache /*!*/ commits;
   final counter = ChangeCounter();
-  TryBuildInfo info;
+  TryBuildInfo /*!*/ info;
   final TestNameLock testNameLock = TestNameLock();
-  String baseRevision;
+  String /*!*/ baseRevision;
   bool success = true;
-  List<SafeDocument> landedResults;
-  Map<String, SafeDocument> lastLandedResultByName = {};
-  final String buildbucketID;
+  List<SafeDocument /*!*/ > landedResults;
+  Map<String /*!*/, SafeDocument /*!*/ > lastLandedResultByName = {};
+  final String /*!*/ buildbucketID;
 
   Tryjob(this.info, this.buildbucketID, this.baseRevision, this.commits,
       this.firestore, this.httpClient);
@@ -79,17 +79,18 @@
         .update();
   }
 
-  bool isNotLandedResult(Map<String, dynamic> change) {
+  bool isNotLandedResult(Map<String, dynamic> /*!*/ change) {
     return !lastLandedResultByName.containsKey(change[fName]) ||
         change[fResult] !=
             lastLandedResultByName[change[fName]].getString(fResult);
   }
 
-  Future<void> process(List<Map<String, dynamic>> results) async {
+  Future<void> process(List<Map<String, dynamic> /*!*/ > results) async {
     await update();
     log('storing ${results.length} change(s)');
-    final resultsByConfiguration = groupBy<Map<String, dynamic>, String>(
-        results, (result) => result['configuration']);
+    final resultsByConfiguration =
+        groupBy<Map<String, dynamic> /*!*/, String /*!*/ >(
+            results, (result) => result['configuration']);
 
     for (final configuration in resultsByConfiguration.keys) {
       if (baseRevision != null && info.previousCommitHash != null) {
@@ -120,7 +121,7 @@
     log(report.join('\n'));
   }
 
-  Future<void> guardedStoreChange(Map<String, dynamic> change) =>
+  Future<void> guardedStoreChange(Map<String, dynamic> /*!*/ change) =>
       testNameLock.guardedCall(storeChange, change);
 
   Future<void> storeChange(Map<String, dynamic> change) async {
@@ -135,26 +136,25 @@
     }
   }
 
-  Future<List<SafeDocument>> fetchLandedResults(String configuration) async {
+  Future<List<SafeDocument /*!*/ >> fetchLandedResults(
+      String configuration) async {
     final resultsBase = await commits.getCommit(info.previousCommitHash);
     final rebaseBase = await commits.getCommit(baseRevision);
-    if (resultsBase.index > rebaseBase.index) {
+    if (resultsBase /*!*/ .index > rebaseBase /*!*/ .index) {
       print('Try build is rebased on $baseRevision, which is before '
           'the commit ${info.previousCommitHash} with CI comparison results');
       return [];
     }
-    final reviews = <int>[];
-    for (var index = resultsBase.index + 1;
-        index <= rebaseBase.index;
-        ++index) {
-      final commit = await commits.getCommitByIndex(index);
-      if (commit.review != null) {
-        reviews.add(commit.review);
-      }
-    }
+    final reviews = [
+      for (var index = resultsBase.index + 1;
+          index <= rebaseBase.index;
+          ++index)
+        (await commits.getCommitByIndex(index))?.review
+    ];
     return [
       for (final landedReview in reviews)
-        ...await firestore.tryResults(landedReview, configuration)
+        if (landedReview != null)
+          ...await firestore.tryResults(landedReview, configuration)
     ];
   }
 }
diff --git a/builder/test/approvals_test.dart b/builder/test/approvals_test.dart
index 7cc792b..c85a444 100644
--- a/builder/test/approvals_test.dart
+++ b/builder/test/approvals_test.dart
@@ -22,40 +22,41 @@
 // To run against the staging database, use a service account.
 // with write access to dart_ci_staging datastore.
 
-FirestoreService firestore;
-http.Client client;
-CommitsCache commitsCache;
+/*late final*/ FirestoreService /*!*/ firestore;
+/*late final*/ http.Client /*!*/ client;
+/*late final*/ CommitsCache /*!*/ commitsCache;
 // The real commits and reviews we will test on, fetched from Firestore.
 // These globals are populated by loadTestCommits().
 const testCommitsStart = 80801;
 const reviewWithComments = '215021';
-String index1; // Index of the final commit in the test range
-String commit1; // Hash of that commit
-String review; // CL number of that commit's Gerrit review
-String lastPatchset; // Final patchset in that review
-String lastPatchsetRef; // 'refs/changes/[review]/[patchset]'
-String patchsetGroup; // First patchset in the final patchset group
-String patchsetGroupRef;
-String earlyPatchset; // Patchset not in the final patchset group
-String earlyPatchsetRef;
+/*late final*/ String /*!*/ index1; // Index of the final commit in the test range
+/*late final*/ String /*!*/ commit1; // Hash of that commit
+/*late final*/ String review; // CL number of that commit's Gerrit review
+/*late final*/ String /*!*/ lastPatchset; // Final patchset in that review
+/*late final*/ String lastPatchsetRef; // 'refs/changes/[review]/[patchset]'
+/*late final*/ String /*!*/
+    patchsetGroup; // First patchset in the final patchset group
+/*late final*/ String patchsetGroupRef;
+/*late final*/ String earlyPatchset; // Patchset not in the final patchset group
+/*late final*/ String earlyPatchsetRef;
 // Earlier commit with a review
-String index2;
-String commit2;
-String review2;
-String patchset2;
-String patchset2Ref;
+/*late final*/ String /*!*/ index2;
+/*late final*/ String /*!*/ commit2;
+/*late final*/ String review2;
+/*late final*/ String /*!*/ patchset2;
+/*late final*/ String patchset2Ref;
 // Commits before commit2
-String index3;
-String commit3;
-String index4;
-String commit4;
+/*late final*/ String index3;
+/*late final*/ String /*!*/ commit3;
+/*late final*/ String index4;
+/*late final*/ String /*!*/ commit4;
 
-final buildersToRemove = <String>{};
-final testsToRemove = <String>{};
+final buildersToRemove = <String /*!*/ >{};
+final testsToRemove = <String /*!*/ >{};
 
 void registerChangeForDeletion(Map<String, dynamic> change) {
-  buildersToRemove.add(change['builder_name']);
-  testsToRemove.add(change['name']);
+  buildersToRemove.add(change['builder_name'] as String /*!*/);
+  testsToRemove.add(change['name'] as String /*!*/);
 }
 
 Future<void> removeBuildersAndResults() async {
@@ -341,7 +342,7 @@
   final resultName = await firestore.findResult(
       change, int.parse(startIndex), int.parse(endIndex));
   expect(resultName, isNotNull);
-  final resultDocument = await firestore.getDocument(resultName);
+  final resultDocument = await firestore.getDocument(resultName /*!*/);
   final data = untagMap(resultDocument.fields);
   expect(data[fName], change[fName]);
   expect(data[fBlamelistStartIndex], int.parse(startIndex));