yaml_edit nullsafety (#107)

* Null safey implimentation

* Adding test for nested parseAt with null value

* Change `parseAt()` signature only YamlNode

* Removing unnecessary ArgumentError.checkNotNull()

* Updating remove() signature and doc

* Change parseAt() orElse documentation

Co-authored-by: Jonas Finnemann Jensen <jopsen@gmail.com>
diff --git a/lib/src/editor.dart b/lib/src/editor.dart
index 8f06d8f..c6f86de 100644
--- a/lib/src/editor.dart
+++ b/lib/src/editor.dart
@@ -120,15 +120,12 @@
 
   factory YamlEditor(String yaml) => YamlEditor._(yaml);
 
-  YamlEditor._(this._yaml) {
-    ArgumentError.checkNotNull(_yaml);
+  YamlEditor._(this._yaml) : _contents = loadYamlNode(_yaml) {
     _initialize();
   }
 
-  /// Loads [_contents] from [_yaml], and traverses the YAML tree formed to
-  /// detect alias nodes.
+  /// Traverses the YAML tree formed to detect alias nodes.
   void _initialize() {
-    _contents = loadYamlNode(_yaml);
     _aliases = {};
 
     /// Performs a DFS on [_contents] to detect alias nodes.
@@ -158,14 +155,17 @@
   ///
   /// If [orElse] is omitted, it defaults to throwing a [ArgumentError].
   ///
-  /// To get `null` when [path] does not point to a value in the [YamlNode]-tree,
-  /// simply pass `orElse: () => null`.
+  /// To get a default value when [path] does not point to a value in the
+  /// [YamlNode]-tree, simply pass `orElse: () => ...`.
   ///
   /// **Example:** (using orElse)
   /// ```dart
   /// final myYamlEditor('{"key": "value"}');
-  /// final value = myYamlEditor.valueAt(['invalid', 'path'], orElse: () => null);
-  /// print(value) // null
+  /// final node = myYamlEditor.valueAt(
+  ///   ['invalid', 'path'],
+  ///   orElse: () => wrapAsYamlNode(null),
+  /// );
+  /// print(node.value); // null
   /// ```
   ///
   /// **Example:** (common usage)
@@ -197,9 +197,7 @@
   /// print(newNode.value); // "YAML"
   /// print(node.value); // "YAML Ain't Markup Language"
   /// ```
-  YamlNode parseAt(Iterable<Object> path, {YamlNode Function() orElse}) {
-    ArgumentError.checkNotNull(path, 'path');
-
+  YamlNode parseAt(Iterable<Object?> path, {YamlNode Function()? orElse}) {
     return _traverse(path, orElse: orElse);
   }
 
@@ -244,9 +242,7 @@
   ///   - test
   ///   - 2
   /// ```
-  void update(Iterable<Object> path, Object value) {
-    ArgumentError.checkNotNull(path, 'path');
-
+  void update(Iterable<Object?> path, Object? value) {
     final valueNode = wrapAsYamlNode(value);
 
     if (path.isEmpty) {
@@ -265,6 +261,9 @@
     final parentNode = _traverse(collectionPath, checkAlias: true);
 
     if (parentNode is YamlList) {
+      if (keyOrIndex is! int) {
+        throw PathError(path, path, parentNode);
+      }
       final expected = wrapAsYamlNode(
         [...parentNode.nodes]..[keyOrIndex] = valueNode,
       );
@@ -294,8 +293,7 @@
   /// final doc = YamlEditor('[0, 1]');
   /// doc.appendToList([], 2); // [0, 1, 2]
   /// ```
-  void appendToList(Iterable<Object> path, Object value) {
-    ArgumentError.checkNotNull(path, 'path');
+  void appendToList(Iterable<Object?> path, Object? value) {
     final yamlList = _traverseToList(path);
 
     insertIntoList(path, yamlList.length, value);
@@ -311,9 +309,7 @@
   /// final doc = YamlEditor('[1, 2]');
   /// doc.prependToList([], 0); // [0, 1, 2]
   /// ```
-  void prependToList(Iterable<Object> path, Object value) {
-    ArgumentError.checkNotNull(path, 'path');
-
+  void prependToList(Iterable<Object?> path, Object? value) {
     insertIntoList(path, 0, value);
   }
 
@@ -329,8 +325,7 @@
   /// final doc = YamlEditor('[0, 2]');
   /// doc.insertIntoList([], 1, 1); // [0, 1, 2]
   /// ```
-  void insertIntoList(Iterable<Object> path, int index, Object value) {
-    ArgumentError.checkNotNull(path, 'path');
+  void insertIntoList(Iterable<Object?> path, int index, Object? value) {
     final valueNode = wrapAsYamlNode(value);
 
     final list = _traverseToList(path, checkAlias: true);
@@ -360,13 +355,8 @@
   /// doc.spliceList([], 1, 0, ['Feb']); // [Jan, Feb, March, April, June]
   /// doc.spliceList([], 4, 1, ['May']); // [Jan, Feb, March, April, May]
   /// ```
-  Iterable<YamlNode> spliceList(Iterable<Object> path, int index,
-      int deleteCount, Iterable<Object> values) {
-    ArgumentError.checkNotNull(path, 'path');
-    ArgumentError.checkNotNull(index, 'index');
-    ArgumentError.checkNotNull(deleteCount, 'deleteCount');
-    ArgumentError.checkNotNull(values, 'values');
-
+  Iterable<YamlNode> spliceList(Iterable<Object?> path, int index,
+      int deleteCount, Iterable<Object?> values) {
     final list = _traverseToList(path, checkAlias: true);
 
     RangeError.checkValueInInterval(index, 0, list.length);
@@ -417,17 +407,15 @@
   /// - 2 # comment 2
   /// '''
   /// ```
-  YamlNode remove(Iterable<Object> path) {
-    ArgumentError.checkNotNull(path, 'path');
-
-    SourceEdit edit;
-    YamlNode expectedNode;
+  YamlNode remove(Iterable<Object?> path) {
+    SourceEdit? edit;
+    var expectedNode = wrapAsYamlNode(null);
     final nodeToRemove = _traverse(path, checkAlias: true);
 
     if (path.isEmpty) {
       edit = SourceEdit(0, _yaml.length, '');
 
-      /// Parsing an empty YAML document returns `null`.
+      /// Parsing an empty YAML document returns YamlScalar with value `null`.
       _performEdit(edit, path, expectedNode);
       return nodeToRemove;
     }
@@ -438,7 +426,7 @@
     final parentNode = _traverse(collectionPath);
 
     if (parentNode is YamlList) {
-      edit = removeInList(this, parentNode, keyOrIndex);
+      edit = removeInList(this, parentNode, keyOrIndex as int);
       expectedNode = wrapAsYamlNode(
         [...parentNode.nodes]..removeAt(keyOrIndex),
       );
@@ -449,7 +437,7 @@
           updatedYamlMap(parentNode, (nodes) => nodes.remove(keyOrIndex));
     }
 
-    _performEdit(edit, collectionPath, expectedNode);
+    _performEdit(edit!, collectionPath, expectedNode);
 
     return nodeToRemove;
   }
@@ -463,11 +451,8 @@
   ///
   /// If [checkAlias] is `true`, throw [AliasError] if an aliased node is
   /// encountered.
-  YamlNode _traverse(Iterable<Object> path,
-      {bool checkAlias = false, YamlNode Function() orElse}) {
-    ArgumentError.checkNotNull(path, 'path');
-    ArgumentError.checkNotNull(checkAlias, 'checkAlias');
-
+  YamlNode _traverse(Iterable<Object?> path,
+      {bool checkAlias = false, YamlNode Function()? orElse}) {
     if (path.isEmpty) return _contents;
 
     var currentNode = _contents;
@@ -481,14 +466,14 @@
       }
 
       if (currentNode is YamlList) {
-        final list = currentNode as YamlList;
+        final list = currentNode;
         if (!isValidIndex(keyOrIndex, list.length)) {
           return _pathErrorOrElse(path, path.take(i + 1), list, orElse);
         }
 
-        currentNode = list.nodes[keyOrIndex];
+        currentNode = list.nodes[keyOrIndex as int];
       } else if (currentNode is YamlMap) {
-        final map = currentNode as YamlMap;
+        final map = currentNode;
 
         if (!containsKey(map, keyOrIndex)) {
           return _pathErrorOrElse(path, path.take(i + 1), map, orElse);
@@ -499,7 +484,7 @@
           if (_aliases.contains(keyNode)) throw AliasError(path, keyNode);
         }
 
-        currentNode = map.nodes[keyNode];
+        currentNode = map.nodes[keyNode]!;
       } else {
         return _pathErrorOrElse(path, path.take(i + 1), currentNode, orElse);
       }
@@ -512,16 +497,14 @@
 
   /// Throws a [PathError] if [orElse] is not provided, returns the result
   /// of invoking the [orElse] function otherwise.
-  YamlNode _pathErrorOrElse(Iterable<Object> path, Iterable<Object> subPath,
-      YamlNode parent, YamlNode Function() orElse) {
+  YamlNode _pathErrorOrElse(Iterable<Object?> path, Iterable<Object?> subPath,
+      YamlNode parent, YamlNode Function()? orElse) {
     if (orElse == null) throw PathError(path, subPath, parent);
     return orElse();
   }
 
   /// Asserts that [node] and none its children are aliases
-  void _assertNoChildAlias(Iterable<Object> path, [YamlNode node]) {
-    ArgumentError.checkNotNull(path, 'path');
-
+  void _assertNoChildAlias(Iterable<Object?> path, [YamlNode? node]) {
     if (node == null) return _assertNoChildAlias(path, _traverse(path));
     if (_aliases.contains(node)) throw AliasError(path, node);
 
@@ -551,10 +534,7 @@
   /// Throws [ArgumentError] if the element at the given path is not a
   /// [YamlList] or if the path is invalid. If [checkAlias] is `true`, and an
   /// aliased node is encountered along [path], an [AliasError] will be thrown.
-  YamlList _traverseToList(Iterable<Object> path, {bool checkAlias = false}) {
-    ArgumentError.checkNotNull(path, 'path');
-    ArgumentError.checkNotNull(checkAlias, 'checkAlias');
-
+  YamlList _traverseToList(Iterable<Object?> path, {bool checkAlias = false}) {
     final possibleList = _traverse(path, checkAlias: true);
 
     if (possibleList is YamlList) {
@@ -572,10 +552,7 @@
   /// tree is equal to our expectations by deep equality of values. Throws an
   /// [AssertionError] if the two trees do not match.
   void _performEdit(
-      SourceEdit edit, Iterable<Object> path, YamlNode expectedNode) {
-    ArgumentError.checkNotNull(edit, 'edit');
-    ArgumentError.checkNotNull(path, 'path');
-
+      SourceEdit edit, Iterable<Object?> path, YamlNode expectedNode) {
     final expectedTree = _deepModify(_contents, path, [], expectedNode);
     final initialYaml = _yaml;
     _yaml = edit.apply(_yaml);
@@ -613,10 +590,8 @@
   /// the whole tree.
   ///
   /// [SourceSpan]s in this new tree are not guaranteed to be accurate.
-  YamlNode _deepModify(YamlNode tree, Iterable<Object> path,
-      Iterable<Object> subPath, YamlNode expectedNode) {
-    ArgumentError.checkNotNull(path, 'path');
-    ArgumentError.checkNotNull(tree, 'tree');
+  YamlNode _deepModify(YamlNode tree, Iterable<Object?> path,
+      Iterable<Object?> subPath, YamlNode expectedNode) {
     RangeError.checkValueInInterval(subPath.length, 0, path.length);
 
     if (path.length == subPath.length) return expectedNode;
@@ -628,7 +603,7 @@
         throw PathError(path, subPath, tree);
       }
 
-      return wrapAsYamlNode([...tree.nodes]..[keyOrIndex] = _deepModify(
+      return wrapAsYamlNode([...tree.nodes]..[keyOrIndex as int] = _deepModify(
           tree.nodes[keyOrIndex],
           path,
           path.take(subPath.length + 1),
diff --git a/lib/src/equality.dart b/lib/src/equality.dart
index 3190812..695a775 100644
--- a/lib/src/equality.dart
+++ b/lib/src/equality.dart
@@ -82,7 +82,7 @@
 
 /// Returns a hashcode for [value] such that structures that are equal by
 /// [deepEquals] will have the same hash code.
-int deepHashCode(Object value) {
+int deepHashCode(Object? value) {
   if (value is Map) {
     const equality = UnorderedIterableEquality();
     return equality.hash(value.keys.map(deepHashCode)) ^
@@ -97,13 +97,13 @@
 }
 
 /// Returns the [YamlNode] corresponding to the provided [key].
-YamlNode getKeyNode(YamlMap map, Object key) {
+YamlNode getKeyNode(YamlMap map, Object? key) {
   return map.nodes.keys.firstWhere((node) => deepEquals(node, key)) as YamlNode;
 }
 
 /// Returns the [YamlNode] after the [YamlNode] corresponding to the provided
 /// [key].
-YamlNode getNextKeyNode(YamlMap map, Object key) {
+YamlNode? getNextKeyNode(YamlMap map, Object? key) {
   final keyIterator = map.nodes.keys.iterator;
   while (keyIterator.moveNext()) {
     if (deepEquals(keyIterator.current, key) && keyIterator.moveNext()) {
@@ -116,11 +116,11 @@
 
 /// Returns the key in [map] that is equal to the provided [key] by the notion
 /// of deep equality.
-Object getKey(Map map, Object key) {
+Object? getKey(Map map, Object? key) {
   return map.keys.firstWhere((k) => deepEquals(k, key));
 }
 
 /// Checks if [map] has any keys equal to the provided [key] by deep equality.
-bool containsKey(Map map, Object key) {
+bool containsKey(Map map, Object? key) {
   return map.keys.where((node) => deepEquals(node, key)).isNotEmpty;
 }
diff --git a/lib/src/errors.dart b/lib/src/errors.dart
index 4a58440..ca1b28a 100644
--- a/lib/src/errors.dart
+++ b/lib/src/errors.dart
@@ -19,15 +19,15 @@
 @sealed
 class PathError extends ArgumentError {
   /// The full path that caused the error
-  final Iterable<Object> path;
+  final Iterable<Object?> path;
 
   /// The subpath that caused the error
-  final Iterable<Object> subPath;
+  final Iterable<Object?> subPath;
 
   /// The last element of [path] that could be traversed.
-  YamlNode parent;
+  YamlNode? parent;
 
-  PathError(this.path, this.subPath, this.parent, [String message])
+  PathError(this.path, this.subPath, this.parent, [String? message])
       : super.value(subPath, 'path', message);
 
   PathError.unexpected(this.path, String message)
@@ -62,7 +62,7 @@
 @sealed
 class AliasError extends UnsupportedError {
   /// The path that caused the error
-  final Iterable<Object> path;
+  final Iterable<Object?> path;
 
   /// The anchor node of the alias
   final YamlNode anchor;
diff --git a/lib/src/list_mutations.dart b/lib/src/list_mutations.dart
index f36fdf2..ef30fff 100644
--- a/lib/src/list_mutations.dart
+++ b/lib/src/list_mutations.dart
@@ -24,8 +24,6 @@
 /// the effect of setting the element at [index] to [newValue] when re-parsed.
 SourceEdit updateInList(
     YamlEditor yamlEdit, YamlList list, int index, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length - 1);
 
   final currValue = list.nodes[index];
@@ -72,9 +70,6 @@
 /// Returns a [SourceEdit] describing the change to be made on [yaml] to achieve
 /// the effect of appending [item] to the list.
 SourceEdit appendIntoList(YamlEditor yamlEdit, YamlList list, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
-
   if (list.style == CollectionStyle.FLOW) {
     return _appendToFlowList(yamlEdit, list, item);
   } else {
@@ -86,8 +81,6 @@
 /// the effect of inserting [item] to the list at [index].
 SourceEdit insertInList(
     YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length);
 
   /// We call the append method if the user wants to append it to the end of the
@@ -106,9 +99,6 @@
 /// Returns a [SourceEdit] describing the change to be made on [yaml] to achieve
 /// the effect of removing the element at [index] when re-parsed.
 SourceEdit removeInList(YamlEditor yamlEdit, YamlList list, int index) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
-
   final nodeToRemove = list.nodes[index];
 
   if (list.style == CollectionStyle.FLOW) {
@@ -122,9 +112,6 @@
 /// the effect of addition [item] into [nodes], noting that this is a flow list.
 SourceEdit _appendToFlowList(
     YamlEditor yamlEdit, YamlList list, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
-
   final valueString = _formatNewFlow(list, item, true);
   return SourceEdit(list.span.end.offset - 1, 0, valueString);
 }
@@ -133,9 +120,6 @@
 /// the effect of addition [item] into [nodes], noting that this is a block list.
 SourceEdit _appendToBlockList(
     YamlEditor yamlEdit, YamlList list, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
-
   var formattedValue = _formatNewBlock(yamlEdit, list, item);
   final yaml = yamlEdit.toString();
   var offset = list.span.end.offset;
@@ -156,9 +140,6 @@
 
 /// Formats [item] into a new node for block lists.
 String _formatNewBlock(YamlEditor yamlEdit, YamlList list, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
-
   final yaml = yamlEdit.toString();
   final listIndentation = getListIndentation(yaml, list);
   final newIndentation = listIndentation + getIndentation(yamlEdit);
@@ -175,9 +156,6 @@
 
 /// Formats [item] into a new node for flow lists.
 String _formatNewFlow(YamlList list, YamlNode item, [bool isLast = false]) {
-  ArgumentError.checkNotNull(list, 'list');
-  ArgumentError.checkNotNull(isLast, 'isLast');
-
   var valueString = yamlEncodeFlowString(item);
   if (list.isNotEmpty) {
     if (isLast) {
@@ -197,8 +175,6 @@
 /// [index] should be non-negative and less than or equal to [length].
 SourceEdit _insertInBlockList(
     YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length);
 
   if (index == list.length) return _appendToBlockList(yamlEdit, list, item);
@@ -220,8 +196,6 @@
 /// [index] should be non-negative and less than or equal to [length].
 SourceEdit _insertInFlowList(
     YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length);
 
   if (index == list.length) return _appendToFlowList(yamlEdit, list, item);
@@ -244,8 +218,6 @@
 /// [index] should be non-negative and less than or equal to [length].
 SourceEdit _removeFromBlockList(
     YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length - 1);
 
   var end = getContentSensitiveEnd(nodeToRemove);
@@ -321,8 +293,6 @@
 /// [index] should be non-negative and less than or equal to [length].
 SourceEdit _removeFromFlowList(
     YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(list, 'list');
   RangeError.checkValueInInterval(index, 0, list.length - 1);
 
   final span = nodeToRemove.span;
diff --git a/lib/src/map_mutations.dart b/lib/src/map_mutations.dart
index 7e6b1c9..2084b59 100644
--- a/lib/src/map_mutations.dart
+++ b/lib/src/map_mutations.dart
@@ -24,10 +24,7 @@
 /// Performs the string operation on [yaml] to achieve the effect of setting
 /// the element at [key] to [newValue] when re-parsed.
 SourceEdit updateInMap(
-    YamlEditor yamlEdit, YamlMap map, Object key, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
+    YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
   if (!containsKey(map, key)) {
     final keyNode = wrapAsYamlNode(key);
 
@@ -47,14 +44,9 @@
 
 /// Performs the string operation on [yaml] to achieve the effect of removing
 /// the element at [key] when re-parsed.
-SourceEdit removeInMap(YamlEditor yamlEdit, YamlMap map, Object key) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
-  if (!containsKey(map, key)) return null;
-
+SourceEdit removeInMap(YamlEditor yamlEdit, YamlMap map, Object? key) {
   final keyNode = getKeyNode(map, key);
-  final valueNode = map.nodes[keyNode];
+  final valueNode = map.nodes[keyNode]!;
 
   if (map.style == CollectionStyle.FLOW) {
     return _removeFromFlowMap(yamlEdit, map, keyNode, valueNode);
@@ -68,9 +60,6 @@
 /// block map.
 SourceEdit _addToBlockMap(
     YamlEditor yamlEdit, YamlMap map, Object key, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
   final yaml = yamlEdit.toString();
   final newIndentation =
       getMapIndentation(yaml, map) + getIndentation(yamlEdit);
@@ -120,9 +109,6 @@
 /// map.
 SourceEdit _addToFlowMap(
     YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
   final keyString = yamlEncodeFlowString(keyNode);
   final valueString = yamlEncodeFlowString(newValue);
 
@@ -147,10 +133,7 @@
 /// the value at [key] with [newValue] when reparsed, bearing in mind that this
 /// is a block map.
 SourceEdit _replaceInBlockMap(
-    YamlEditor yamlEdit, YamlMap map, Object key, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
+    YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
   final yaml = yamlEdit.toString();
   final lineEnding = getLineEnding(yaml);
   final newIndentation =
@@ -167,7 +150,7 @@
 
   /// +1 accounts for the colon
   final start = keyNode.span.end.offset + 1;
-  var end = getContentSensitiveEnd(map.nodes[key]);
+  var end = getContentSensitiveEnd(map.nodes[key]!);
 
   /// `package:yaml` parses empty nodes in a way where the start/end of the
   /// empty value node is the end of the key node, so we have to adjust for
@@ -181,11 +164,8 @@
 /// the value at [key] with [newValue] when reparsed, bearing in mind that this
 /// is a flow map.
 SourceEdit _replaceInFlowMap(
-    YamlEditor yamlEdit, YamlMap map, Object key, YamlNode newValue) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-
-  final valueSpan = map.nodes[key].span;
+    YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
+  final valueSpan = map.nodes[key]!.span;
   final valueString = yamlEncodeFlowString(newValue);
 
   return SourceEdit(valueSpan.start.offset, valueSpan.length, valueString);
@@ -195,11 +175,6 @@
 /// the [key] from the map, bearing in mind that this is a block map.
 SourceEdit _removeFromBlockMap(
     YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-  ArgumentError.checkNotNull(keyNode, 'keyNode');
-  ArgumentError.checkNotNull(valueNode, 'valueNode');
-
   final keySpan = keyNode.span;
   var end = getContentSensitiveEnd(valueNode);
   final yaml = yamlEdit.toString();
@@ -247,11 +222,6 @@
 /// the [key] from the map, bearing in mind that this is a flow map.
 SourceEdit _removeFromFlowMap(
     YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
-  ArgumentError.checkNotNull(yamlEdit, 'yamlEdit');
-  ArgumentError.checkNotNull(map, 'map');
-  ArgumentError.checkNotNull(keyNode, 'keyNode');
-  ArgumentError.checkNotNull(valueNode, 'valueNode');
-
   var start = keyNode.span.start.offset;
   var end = valueNode.span.end.offset;
   final yaml = yamlEdit.toString();
diff --git a/lib/src/source_edit.dart b/lib/src/source_edit.dart
index f8e6ab9..7721ba9 100644
--- a/lib/src/source_edit.dart
+++ b/lib/src/source_edit.dart
@@ -43,9 +43,6 @@
       SourceEdit._(offset, length, replacement);
 
   SourceEdit._(this.offset, this.length, this.replacement) {
-    ArgumentError.checkNotNull(offset, 'offset');
-    ArgumentError.checkNotNull(length, 'length');
-    ArgumentError.checkNotNull(replacement, 'replacement');
     RangeError.checkNotNegative(offset);
     RangeError.checkNotNegative(length);
   }
@@ -77,8 +74,6 @@
   /// final sourceEdit = SourceEdit.fromJson(edit);
   /// ```
   factory SourceEdit.fromJson(Map<String, dynamic> json) {
-    ArgumentError.checkNotNull(json, 'json');
-
     if (json is Map) {
       final offset = json['offset'];
       final length = json['length'];
@@ -130,9 +125,6 @@
   /// Language"
   /// ```
   static String applyAll(String original, Iterable<SourceEdit> edits) {
-    ArgumentError.checkNotNull(original, 'original');
-    ArgumentError.checkNotNull(edits, 'edits');
-
     return edits.fold(original, (current, edit) => edit.apply(current));
   }
 
@@ -146,8 +138,6 @@
   /// print(edit.apply(originalString)); // 'foo: barbar'
   /// ```
   String apply(String original) {
-    ArgumentError.checkNotNull(original, 'original');
-
     return original.replaceRange(offset, offset + length, replacement);
   }
 }
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 65bdac5..1143d1b 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -22,7 +22,7 @@
 /// an escape sequence, it can only be detected when in a double-quoted
 /// sequence. Plain strings may also be misinterpreted by the YAML parser (e.g.
 /// ' null').
-String _tryYamlEncodePlain(Object value) {
+String _tryYamlEncodePlain(Object? value) {
   if (value is YamlNode) {
     AssertionError(
         'YamlNodes should not be passed directly into getSafeString!');
@@ -50,8 +50,6 @@
 /// Checks if [string] has unprintable characters according to
 /// [unprintableCharCodes].
 bool _hasUnprintableCharacters(String string) {
-  ArgumentError.checkNotNull(string, 'string');
-
   final codeUnits = string.codeUnits;
 
   for (final key in unprintableCharCodes.keys) {
@@ -66,8 +64,6 @@
 ///
 /// See 5.7 Escaped Characters https://yaml.org/spec/1.2/spec.html#id2776092
 String _yamlEncodeDoubleQuoted(String string) {
-  ArgumentError.checkNotNull(string, 'string');
-
   final buffer = StringBuffer();
   for (final codeUnit in string.codeUnits) {
     if (doubleQuoteEscapeChars[codeUnit] != null) {
@@ -86,8 +82,6 @@
 /// It is important that we ensure that [string] is free of unprintable
 /// characters by calling [assertValidScalar] before invoking this function.
 String _tryYamlEncodeSingleQuoted(String string) {
-  ArgumentError.checkNotNull(string, 'string');
-
   final result = string.replaceAll('\'', '\'\'');
   return '\'$result\'';
 }
@@ -97,10 +91,6 @@
 /// It is important that we ensure that [string] is free of unprintable
 /// characters by calling [assertValidScalar] before invoking this function.
 String _tryYamlEncodeFolded(String string, int indentation, String lineEnding) {
-  ArgumentError.checkNotNull(string, 'string');
-  ArgumentError.checkNotNull(indentation, 'indentation');
-  ArgumentError.checkNotNull(lineEnding, 'lineEnding');
-
   String result;
 
   final trimmedString = string.trimRight();
@@ -126,10 +116,6 @@
 /// characters by calling [assertValidScalar] before invoking this function.
 String _tryYamlEncodeLiteral(
     String string, int indentation, String lineEnding) {
-  ArgumentError.checkNotNull(string, 'string');
-  ArgumentError.checkNotNull(indentation, 'indentation');
-  ArgumentError.checkNotNull(lineEnding, 'lineEnding');
-
   final result = '|-\n$string';
 
   /// Assumes the user did not try to account for windows documents by using
@@ -175,9 +161,6 @@
 /// options.
 String yamlEncodeBlockScalar(
     YamlNode value, int indentation, String lineEnding) {
-  ArgumentError.checkNotNull(indentation, 'indentation');
-  ArgumentError.checkNotNull(lineEnding, 'lineEnding');
-
   if (value is YamlScalar) {
     assertValidScalar(value.value);
 
@@ -243,9 +226,6 @@
 /// If [value] is a [YamlNode], we respect its [style] parameter.
 String yamlEncodeBlockString(
     YamlNode value, int indentation, String lineEnding) {
-  ArgumentError.checkNotNull(indentation, 'indentation');
-  ArgumentError.checkNotNull(lineEnding, 'lineEnding');
-
   const additionalIndentation = 2;
 
   if (!isBlockNode(value)) return yamlEncodeFlowString(value);
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index c0d70bf..5550c75 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -23,8 +23,6 @@
 /// This function is also capable of detecting if non-printable characters are in
 /// [string].
 bool isDangerousString(String string) {
-  ArgumentError.checkNotNull(string, 'string');
-
   try {
     if (loadYamlNode(string).value != string) {
       return true;
@@ -43,7 +41,7 @@
 /// Asserts that [value] is a valid scalar according to YAML.
 ///
 /// A valid scalar is a number, String, boolean, or null.
-void assertValidScalar(Object value) {
+void assertValidScalar(Object? value) {
   if (value is num || value is String || value is bool || value == null) {
     return;
   }
@@ -56,8 +54,6 @@
 /// [ScalarStyle.ANY] and [CollectionStyle.ANY] are considered to be block styling
 /// by default for maximum flexibility.
 bool isBlockNode(YamlNode node) {
-  ArgumentError.checkNotNull(node, 'node');
-
   if (node is YamlScalar) {
     if (node.style == ScalarStyle.LITERAL ||
         node.style == ScalarStyle.FOLDED ||
@@ -79,8 +75,6 @@
 /// Returns the content sensitive ending offset of [yamlNode] (i.e. where the last
 /// meaningful content happens)
 int getContentSensitiveEnd(YamlNode yamlNode) {
-  ArgumentError.checkNotNull(yamlNode, 'yamlNode');
-
   if (yamlNode is YamlList) {
     if (yamlNode.style == CollectionStyle.FLOW) {
       return yamlNode.span.end.offset;
@@ -102,7 +96,7 @@
 bool isCollection(Object item) => item is Map || item is List;
 
 /// Checks if [index] is [int], >=0, < [length]
-bool isValidIndex(Object index, int length) {
+bool isValidIndex(Object? index, int length) {
   return index is int && index >= 0 && index < length;
 }
 
@@ -121,7 +115,7 @@
 ///
 /// Mainly used with [wrapAsYamlNode] to allow for a reasonable
 /// implementation of [SourceSpan.message].
-SourceSpan shellSpan(Object sourceUrl) {
+SourceSpan shellSpan(Object? sourceUrl) {
   final shellSourceLocation = SourceLocation(0, sourceUrl: sourceUrl);
   return SourceSpanBase(shellSourceLocation, shellSourceLocation, '');
 }
@@ -140,8 +134,6 @@
 ///
 /// Returns the length of [map] if the keys in [map] are not in alphabetical order.
 int getMapInsertionIndex(YamlMap map, Object newKey) {
-  ArgumentError.checkNotNull(map, 'map');
-
   final keys = map.nodes.keys.map((k) => k.toString()).toList();
 
   for (var i = 1; i < keys.length; i++) {
@@ -150,7 +142,8 @@
     }
   }
 
-  final insertionIndex = keys.indexWhere((key) => key.compareTo(newKey) > 0);
+  final insertionIndex =
+      keys.indexWhere((key) => key.compareTo(newKey as String) > 0);
 
   if (insertionIndex != -1) return insertionIndex;
 
@@ -158,7 +151,7 @@
 }
 
 /// Returns the [style] property of [target], if it is a [YamlNode]. Otherwise return null.
-Object getStyle(Object target) {
+Object? getStyle(Object target) {
   if (target is YamlNode) {
     return (target as dynamic).style;
   }
@@ -175,7 +168,7 @@
 /// candidates, we choose the candidate closest to the start of [yaml].
 int getIndentation(YamlEditor editor) {
   final node = editor.parseAt([]);
-  Iterable<YamlNode> children;
+  Iterable<YamlNode>? children;
   var indentation = 2;
 
   if (node is YamlMap && node.style == CollectionStyle.BLOCK) {
@@ -205,8 +198,6 @@
 ///
 /// Throws [UnsupportedError] if an empty block map is passed in.
 int getListIndentation(String yaml, YamlList list) {
-  ArgumentError.checkNotNull(list, 'list');
-
   if (list.style == CollectionStyle.FLOW) return 0;
 
   /// An empty block map doesn't really exist.
@@ -226,8 +217,6 @@
 /// Gets the indentation level of [map]. This is 0 if it is a flow map,
 /// but returns the number of spaces before the keys for block maps.
 int getMapIndentation(String yaml, YamlMap map) {
-  ArgumentError.checkNotNull(map, 'map');
-
   if (map.style == CollectionStyle.FLOW) return 0;
 
   /// An empty block map doesn't really exist.
diff --git a/lib/src/wrap.dart b/lib/src/wrap.dart
index f82bd72..e95c43f 100644
--- a/lib/src/wrap.dart
+++ b/lib/src/wrap.dart
@@ -24,14 +24,12 @@
 /// Returns a new [YamlMap] constructed by applying [update] onto the [nodes]
 /// of this [YamlMap].
 YamlMap updatedYamlMap(YamlMap map, Function(Map) update) {
-  ArgumentError.checkNotNull(map, 'map');
-
   final dummyMap = deepEqualsMap();
   dummyMap.addAll(map.nodes);
 
   update(dummyMap);
 
-  return wrapAsYamlNode(dummyMap);
+  return wrapAsYamlNode(dummyMap) as YamlMap;
 }
 
 /// Wraps [value] into a [YamlNode].
@@ -43,7 +41,7 @@
 ///
 /// If a [YamlNode] is passed in, no further wrapping will be done, and the
 /// [collectionStyle]/[scalarStyle] will not be applied.
-YamlNode wrapAsYamlNode(Object value,
+YamlNode wrapAsYamlNode(Object? value,
     {CollectionStyle collectionStyle = CollectionStyle.ANY,
     ScalarStyle scalarStyle = ScalarStyle.ANY}) {
   if (value is YamlScalar) {
@@ -65,15 +63,12 @@
 
     return value;
   } else if (value is Map) {
-    ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
     return YamlMapWrap(value, collectionStyle: collectionStyle);
   } else if (value is List) {
-    ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
     return YamlListWrap(value, collectionStyle: collectionStyle);
   } else {
     assertValidScalar(value);
 
-    ArgumentError.checkNotNull(scalarStyle, 'scalarStyle');
     return YamlScalarWrap(value, style: scalarStyle);
   }
 }
@@ -91,10 +86,8 @@
   @override
   final dynamic value;
 
-  YamlScalarWrap(this.value, {this.style = ScalarStyle.ANY, Object sourceUrl})
-      : span = shellSpan(sourceUrl) {
-    ArgumentError.checkNotNull(style, 'scalarStyle');
-  }
+  YamlScalarWrap(this.value, {this.style = ScalarStyle.ANY, Object? sourceUrl})
+      : span = shellSpan(sourceUrl);
 
   @override
   String toString() => value.toString();
@@ -117,9 +110,7 @@
 
   factory YamlMapWrap(Map dartMap,
       {CollectionStyle collectionStyle = CollectionStyle.ANY,
-      Object sourceUrl}) {
-    ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
-
+      Object? sourceUrl}) {
     final wrappedMap = deepEqualsMap<dynamic, YamlNode>();
 
     for (final entry in dartMap.entries) {
@@ -133,12 +124,12 @@
   }
 
   YamlMapWrap._(this.nodes,
-      {CollectionStyle style = CollectionStyle.ANY, Object sourceUrl})
+      {CollectionStyle style = CollectionStyle.ANY, Object? sourceUrl})
       : span = shellSpan(sourceUrl),
         style = nodes.isEmpty ? CollectionStyle.FLOW : style;
 
   @override
-  dynamic operator [](Object key) => nodes[key]?.value;
+  dynamic operator [](Object? key) => nodes[key]?.value;
 
   @override
   Iterable get keys => nodes.keys.map((node) => node.value);
@@ -170,16 +161,14 @@
 
   factory YamlListWrap(List dartList,
       {CollectionStyle collectionStyle = CollectionStyle.ANY,
-      Object sourceUrl}) {
-    ArgumentError.checkNotNull(collectionStyle, 'collectionStyle');
-
+      Object? sourceUrl}) {
     final wrappedList = dartList.map(wrapAsYamlNode).toList();
     return YamlListWrap._(wrappedList,
         style: collectionStyle, sourceUrl: sourceUrl);
   }
 
   YamlListWrap._(this.nodes,
-      {CollectionStyle style = CollectionStyle.ANY, Object sourceUrl})
+      {CollectionStyle style = CollectionStyle.ANY, Object? sourceUrl})
       : span = shellSpan(sourceUrl),
         style = nodes.isEmpty ? CollectionStyle.FLOW : style;
 
diff --git a/pubspec.yaml b/pubspec.yaml
index d175b67..91ef188 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -8,10 +8,10 @@
   collection: ^1.14.11
   meta: ^1.1.8
   source_span: ^1.7.0
-  yaml: ^2.2.1
+  yaml: ^3.1.0
 dev_dependencies:
   path: ^1.6.2
   pedantic: ^1.9.0
   test: ^1.14.4
 environment:
-  sdk: ">=2.4.0 <3.0.0"
+  sdk: ">=2.12.0-0 <3.0.0"
diff --git a/test/append_test.dart b/test/append_test.dart
index 212c749..4b80ff0 100644
--- a/test/append_test.dart
+++ b/test/append_test.dart
@@ -49,6 +49,28 @@
       expectYamlBuilderValue(doc, [0, 1, 2, 3, 4]);
     });
 
+    test('null path', () {
+      final doc = YamlEditor('''
+~:
+  - 0
+  - 1
+  - 2
+  - 3
+''');
+      doc.appendToList([null], 4);
+      expect(doc.toString(), equals('''
+~:
+  - 0
+  - 1
+  - 2
+  - 3
+  - 4
+'''));
+      expectYamlBuilderValue(doc, {
+        null: [0, 1, 2, 3, 4]
+      });
+    });
+
     test('element to simple block list ', () {
       final doc = YamlEditor('''
 - 0
@@ -123,7 +145,7 @@
     test('nested', () {
       final yamlEditor = YamlEditor('''
 a:
-  1: 
+  1:
     - null
   2: null
 ''');
@@ -131,7 +153,7 @@
 
       expect(yamlEditor.toString(), equals('''
 a:
-  1: 
+  1:
     - null
     - false
   2: null
@@ -147,6 +169,13 @@
       expectYamlBuilderValue(doc, [0, 1, 2, 3]);
     });
 
+    test('null value', () {
+      final doc = YamlEditor('[0, 1, 2]');
+      doc.appendToList([], null);
+      expect(doc.toString(), equals('[0, 1, 2, null]'));
+      expectYamlBuilderValue(doc, [0, 1, 2, null]);
+    });
+
     test('empty ', () {
       final doc = YamlEditor('[]');
       doc.appendToList([], 0);
diff --git a/test/golden_test.dart b/test/golden_test.dart
index 7715069..305aa51 100644
--- a/test/golden_test.dart
+++ b/test/golden_test.dart
@@ -29,7 +29,7 @@
   final packageUri = await Isolate.resolvePackageUri(
       Uri.parse('package:yaml_edit/yaml_edit.dart'));
 
-  final testdataUri = packageUri.resolve('../test/testdata/');
+  final testdataUri = packageUri!.resolve('../test/testdata/');
   final inputDirectory = Directory.fromUri(testdataUri.resolve('input/'));
   final goldDirectoryUri = testdataUri.resolve('output/');
 
diff --git a/test/parse_test.dart b/test/parse_test.dart
index ea2ae57..a743b90 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.dart
@@ -50,11 +50,11 @@
   });
 
   group('orElse provides a default value', () {
-    test('simple example with null return ', () {
+    test('simple example with null node return ', () {
       final doc = YamlEditor('{a: {d: 4}, c: ~}');
-      final result = doc.parseAt(['b'], orElse: () => null);
+      final result = doc.parseAt(['b'], orElse: () => wrapAsYamlNode(null));
 
-      expect(result, equals(null));
+      expect(result.value, equals(null));
     });
 
     test('simple example with map return', () {
diff --git a/test/prepend_test.dart b/test/prepend_test.dart
index 52256ef..ab69fc2 100644
--- a/test/prepend_test.dart
+++ b/test/prepend_test.dart
@@ -39,6 +39,13 @@
       expectYamlBuilderValue(doc, [0, 1, 2]);
     });
 
+    test('null value', () {
+      final doc = YamlEditor('[1, 2]');
+      doc.prependToList([], null);
+      expect(doc.toString(), equals('[null, 1, 2]'));
+      expectYamlBuilderValue(doc, [null, 1, 2]);
+    });
+
     test('with spaces (1)', () {
       final doc = YamlEditor('[ 1 , 2 ]');
       doc.prependToList([], 0);
diff --git a/test/random_test.dart b/test/random_test.dart
index a334335..2b5af99 100644
--- a/test/random_test.dart
+++ b/test/random_test.dart
@@ -79,7 +79,7 @@
   /// Maximum depth of random YAML collection generated.
   final int maxDepth;
 
-  _Generator({int seed, this.maxDepth = 5}) : r = Random(seed ?? 42);
+  _Generator({int? seed, this.maxDepth = 5}) : r = Random(seed ?? 42);
 
   int nextInt([int max = maxInt]) => r.nextInt(max);
 
@@ -107,7 +107,7 @@
   }
 
   /// Generates a new scalar recognizable by YAML.
-  Object nextScalar() {
+  Object? nextScalar() {
     final typeIndex = nextInt(5);
 
     switch (typeIndex) {
@@ -125,7 +125,8 @@
   }
 
   YamlScalar nextYamlScalar() {
-    return wrapAsYamlNode(nextScalar(), scalarStyle: nextScalarStyle());
+    return wrapAsYamlNode(nextScalar(), scalarStyle: nextScalarStyle())
+        as YamlScalar;
   }
 
   /// Generates the next [YamlList], with the current [depth].
@@ -137,7 +138,8 @@
       list.add(nextYamlNode(depth + 1));
     }
 
-    return wrapAsYamlNode(list, collectionStyle: nextCollectionStyle());
+    return wrapAsYamlNode(list, collectionStyle: nextCollectionStyle())
+        as YamlList;
   }
 
   /// Generates the next [YamlList], with the current [depth].
@@ -149,7 +151,8 @@
       nodes[nextYamlNode(depth + 1)] = nextYamlScalar();
     }
 
-    return wrapAsYamlNode(nodes, collectionStyle: nextCollectionStyle());
+    return wrapAsYamlNode(nodes, collectionStyle: nextCollectionStyle())
+        as YamlMap;
   }
 
   /// Returns a [YamlNode], with it being a [YamlScalar] 80% of the time, a
@@ -217,7 +220,7 @@
             break;
           case YamlModificationMethod.splice:
             args.add(nextInt(node.length + 1));
-            args.add(nextInt(node.length + 1 - args[0]));
+            args.add(nextInt(node.length + 1 - args[0] as int));
             args.add(nextYamlList(0));
             editor.spliceList(path, args[0], args[1], args[2]);
             break;
@@ -240,7 +243,7 @@
         editor.update(path, value);
         return;
       }
-    } catch (error) {
+    } catch (error, stacktrace) {
       print('''
 Failed to call $method on:
 $initialString
@@ -250,7 +253,9 @@
 $path
 
 Error Details:
-${error.message}
+${error}
+
+${stacktrace}
 ''');
       rethrow;
     }
@@ -262,8 +267,8 @@
   ///
   /// At every node, we return the path to the node if the node has no children.
   /// Otherwise, we return at a 50% chance, or traverse to one random child.
-  List<Object> findPath(YamlEditor editor) {
-    final path = [];
+  List<Object?> findPath(YamlEditor editor) {
+    final path = <Object?>[];
 
     // 50% chance of stopping at the collection
     while (nextBool()) {
diff --git a/test/remove_test.dart b/test/remove_test.dart
index adf4338..3ed2fed 100644
--- a/test/remove_test.dart
+++ b/test/remove_test.dart
@@ -39,6 +39,15 @@
       expect(() => doc.remove(['d']), throwsPathError);
     });
 
+    test('PathError if collectionPath is invalid in nested path', () {
+      final doc = YamlEditor('''
+a:
+  b: 'foo'
+''');
+
+      expect(() => doc.remove(['d']), throwsPathError);
+    });
+
     test('PathError if collectionPath is invalid - list', () {
       final doc = YamlEditor('''
 [1, 2, 3]
@@ -46,6 +55,30 @@
 
       expect(() => doc.remove([4]), throwsPathError);
     });
+
+    test('PathError in list if using a non-integer as index', () {
+      final doc = YamlEditor("{ a: ['b', 'c'] }");
+      expect(() => doc.remove(['a', 'b']), throwsPathError);
+    });
+
+    test('PathError if path is invalid', () {
+      final doc = YamlEditor("{ a: ['b', 'c'] }");
+      expect(() => doc.remove(['a', 0, '1']), throwsPathError);
+    });
+  });
+
+  group('returns', () {
+    test('returns the removed node when successful', () {
+      final doc = YamlEditor('{ a: { b: foo } }');
+      final node = doc.remove(['a', 'b']);
+      expect(node.value, equals('foo'));
+    });
+
+    test('returns null-value node when doc is empty and path is empty', () {
+      final doc = YamlEditor('');
+      final node = doc.remove([]);
+      expect(node.value, equals(null));
+    });
   });
 
   test('empty path should clear string', () {
@@ -75,7 +108,7 @@
     test('empty value', () {
       final doc = YamlEditor('''
 a: 1
-b: 
+b:
 c: 3
 ''');
       doc.remove(['b']);
@@ -88,7 +121,7 @@
     test('empty value (2)', () {
       final doc = YamlEditor('''
 - a: 1
-  b: 
+  b:
   c: 3
 ''');
       doc.remove([0, 'b']);
@@ -101,7 +134,7 @@
     test('empty value (3)', () {
       final doc = YamlEditor('''
 - a: 1
-  b: 
+  b:
 
   c: 3
 ''');
@@ -143,14 +176,14 @@
 
     test('final element in nested map', () {
       final doc = YamlEditor('''
-a: 
+a:
   aa: 11
   bb: 22
 b: 2
 ''');
       doc.remove(['a', 'bb']);
       expect(doc.toString(), equals('''
-a: 
+a:
   aa: 11
 b: 2
 '''));
@@ -181,7 +214,7 @@
     test('nested', () {
       final doc = YamlEditor('''
 a: 1
-b: 
+b:
   d: 4
   e: 5
 c: 3
@@ -189,7 +222,7 @@
       doc.remove(['b', 'd']);
       expect(doc.toString(), equals('''
 a: 1
-b: 
+b:
   e: 5
 c: 3
 '''));
@@ -250,7 +283,7 @@
     test('empty value', () {
       final doc = YamlEditor('''
 - 0
-- 
+-
 - 2
 ''');
       doc.remove([1]);
@@ -272,13 +305,13 @@
 
     test('last element should return flow empty list (2)', () {
       final doc = YamlEditor('''
-a: 
+a:
   - 1
 b: [3]
 ''');
       doc.remove(['a', 0]);
       expect(doc.toString(), equals('''
-a: 
+a:
   []
 b: [3]
 '''));
@@ -286,16 +319,16 @@
 
     test('last element should return flow empty list (3)', () {
       final doc = YamlEditor('''
-a: 
+a:
   - 1
-b: 
+b:
   - 3
 ''');
       doc.remove(['a', 0]);
       expect(doc.toString(), equals('''
-a: 
+a:
   []
-b: 
+b:
   - 3
 '''));
     });
@@ -450,12 +483,12 @@
     test('nested list (5)', () {
       final doc = YamlEditor('''
 - - 0
-  - 
+  -
     1
 ''');
       doc.remove([0, 0]);
       expect(doc.toString(), equals('''
-- - 
+- -
     1
 '''));
       expectYamlBuilderValue(doc, [
@@ -467,13 +500,13 @@
       final doc = YamlEditor('''
 - - 0 # -
   # -
-  - 
+  -
     1
 ''');
       doc.remove([0, 0]);
       expect(doc.toString(), equals('''
 -   # -
-  - 
+  -
     1
 '''));
       expectYamlBuilderValue(doc, [
@@ -499,14 +532,14 @@
 
     test('nested map (2)', () {
       final doc = YamlEditor('''
-- a: 
+- a:
     - 0
     - 1
   c: d
 ''');
       doc.remove([0, 'a', 1]);
       expect(doc.toString(), equals('''
-- a: 
+- a:
     - 0
   c: d
 '''));
diff --git a/test/test_case.dart b/test/test_case.dart
index 58bab5f..aa80d9e 100644
--- a/test/test_case.dart
+++ b/test/test_case.dart
@@ -81,9 +81,9 @@
   final Uri goldenUri;
   final List<String> states = [];
 
-  String info;
-  YamlEditor yamlBuilder;
-  List<_YamlModification> modifications;
+  late String info;
+  late YamlEditor yamlBuilder;
+  late List<_YamlModification> modifications;
 
   String inputLineEndings = '\n';
 
@@ -223,37 +223,40 @@
 
 /// Converts a [YamlNode] into a Dart object.
 dynamic _getValueFromYamlNode(YamlNode node) {
-  switch (node.runtimeType) {
-    case YamlList:
-      return _getValueFromYamlList(node);
-    case YamlMap:
-      return _getValueFromYamlMap(node);
-    default:
-      return node.value;
+  if (node is YamlList) {
+    return _getValueFromYamlList(node);
   }
+  if (node is YamlMap) {
+    return _getValueFromYamlMap(node);
+  }
+  return node.value;
+}
+
+List<T> _onlyType<T>(List<dynamic> rawPath) {
+  return rawPath.whereType<T>().toList();
 }
 
 /// Converts the list of modifications from the raw input to [_YamlModification]
 /// objects.
 List<_YamlModification> _parseModifications(List<dynamic> modifications) {
   return modifications.map((mod) {
-    Object value;
-    int index;
-    int deleteCount;
+    Object? value;
+    var index = 0;
+    var deleteCount = 0;
     final method = _getModificationMethod(mod[0] as String);
 
-    final path = mod[1] as List;
+    final path = mod[1];
 
     if (method == YamlModificationMethod.appendTo ||
         method == YamlModificationMethod.update ||
         method == YamlModificationMethod.prependTo) {
       value = mod[2];
     } else if (method == YamlModificationMethod.insert) {
-      index = mod[2];
+      index = mod[2] as int;
       value = mod[3];
     } else if (method == YamlModificationMethod.splice) {
-      index = mod[2];
-      deleteCount = mod[3];
+      index = mod[2] as int;
+      deleteCount = mod[3] as int;
 
       if (mod[4] is! List) {
         throw ArgumentError('Invalid array ${mod[4]} used in splice');
@@ -292,7 +295,7 @@
 /// Class representing an abstract YAML modification to be performed
 class _YamlModification {
   final YamlModificationMethod method;
-  final List<dynamic> path;
+  final List<Object?> path;
   final int index;
   final dynamic value;
   final int deleteCount;
diff --git a/test/test_utils.dart b/test/test_utils.dart
index a87b18d..9f030f1 100644
--- a/test/test_utils.dart
+++ b/test/test_utils.dart
@@ -31,7 +31,7 @@
 }
 
 /// Asserts that [builder] has the same internal value as [expected].
-void expectDeepEquals(Object actual, Object expected) {
+void expectDeepEquals(Object? actual, Object expected) {
   expect(
       actual, predicate((actual) => deepEquals(actual, expected), '$expected'));
 }
diff --git a/test/update_test.dart b/test/update_test.dart
index 685d6bd..64a9b6b 100644
--- a/test/update_test.dart
+++ b/test/update_test.dart
@@ -34,6 +34,11 @@
       final doc = YamlEditor("- YAML Ain't Markup Language");
       expect(() => doc.update([0, 'a'], 'a'), throwsPathError);
     });
+
+    test('PathError in list if using a non-integer as index', () {
+      final doc = YamlEditor("{ a: ['b', 'c'] }");
+      expect(() => doc.update(['a', 'b'], 'x'), throwsPathError);
+    });
   });
 
   group('works on top-level', () {