Merge null_safety branch into master (#89)

diff --git a/.travis.yml b/.travis.yml
index 5afa17a..fc70ed4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,34 +1,30 @@
 language: dart
 
 dart:
-  - 2.0.0
   - dev
 
-dart_task:
-  - test: --platform vm,chrome
-
-matrix:
+jobs:
   include:
-  # Run tests on mac and windows – but just dev SDKs, no browser
   - dart: dev
-    dart_task: test
+    script: pub run --enable-experiment=non-nullable test
+    os: linux
+  - dart: dev
+    script: pub run --enable-experiment=non-nullable test
     os: windows
   - dart: dev
-    dart_task: test
+    script: pub run --enable-experiment=non-nullable test
     os: osx
-  # Only validate formatting using the dev release
+  - dart_task: dartfmt
+  - dart_task:
+      dartanalyzer: --enable-experiment=non-nullable --fatal-infos --fatal-warnings .
   - dart: dev
-    dart_task: dartfmt
-  - dart: dev
-    dart_task:
-      dartanalyzer: --fatal-infos --fatal-warnings .
-  - dart: 2.0.0
-    dart_task:
-      dartanalyzer: --fatal-warnings .
+    script: pub run --enable-experiment=non-nullable test -p chrome
+    os: linux
 
 # Only building master means that we don't run two builds for each pull request.
+# Temporarily adding `null_safety`
 branches:
-  only: [master]
+  only: [master, null_safety]
 
 cache:
  directories:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c962e1..5c64f66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
-## 1.7.1-dev
+## 1.8.0-nullsafety
+
+* Migrate to null safety.
 
 ## 1.7.0
 
diff --git a/analysis_options.yaml b/analysis_options.yaml
index ffabc48..743c74b 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -4,6 +4,9 @@
   strong-mode:
     implicit-casts: false
 
+  enable-experiment:
+    - non-nullable
+
 linter:
   rules:
     - avoid_catching_errors
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index a7fbb7a..fe1e522 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -32,14 +32,14 @@
 };
 
 /// The command line arguments passed to this script.
-List<String> arguments;
+late final List<String> arguments;
 
 void main(List<String> args) {
   arguments = args;
 
   for (var style in [p.Style.posix, p.Style.url, p.Style.windows]) {
     final context = p.Context(style: style);
-    final files = genericPaths.toList()..addAll(platformPaths[style]);
+    final files = <String>[...genericPaths, ...platformPaths[style]!];
 
     void benchmark(String name, Function function) {
       runBenchmark('${style.name}-$name', 100000, () {
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 0000000..340d63c
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,13 @@
+# See https://github.com/dart-lang/build/tree/master/build_web_compilers#configuration
+# Matches previous configuration in pubspec.yaml - transformers - $dart2js
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        # These are globs for the entrypoints you want to compile.
+        generate_for:
+          include:
+          - test/**.dart
+          exclude:
+          - test/io_test.dart
+          - test/**vm_test.dart
diff --git a/lib/path.dart b/lib/path.dart
index 5ce50d6..a4a8be9 100644
--- a/lib/path.dart
+++ b/lib/path.dart
@@ -72,13 +72,13 @@
   try {
     uri = Uri.base;
   } on Exception {
-    if (_current != null) return _current;
+    if (_current != null) return _current!;
     rethrow;
   }
 
   // Converting the base URI to a file path is pretty slow, and the base URI
   // rarely changes in practice, so we cache the result here.
-  if (uri == _currentUriBase) return _current;
+  if (uri == _currentUriBase) return _current!;
   _currentUriBase = uri;
 
   if (Style.platform == Style.url) {
@@ -91,19 +91,19 @@
     assert(path[lastIndex] == '/' || path[lastIndex] == '\\');
     _current = lastIndex == 0 ? path : path.substring(0, lastIndex);
   }
-  return _current;
+  return _current!;
 }
 
 /// The last value returned by [Uri.base].
 ///
 /// This is used to cache the current working directory.
-Uri _currentUriBase;
+Uri? _currentUriBase;
 
 /// The last known value of the current working directory.
 ///
 /// This is cached because [current] is called frequently but rarely actually
 /// changes.
-String _current;
+String? _current;
 
 /// Gets the path separator for the current platform. This is `\` on Windows
 /// and `/` on other platforms (including the browser).
@@ -117,12 +117,12 @@
 ///
 /// Does not [normalize] or [canonicalize] paths.
 String absolute(String part1,
-        [String part2,
-        String part3,
-        String part4,
-        String part5,
-        String part6,
-        String part7]) =>
+        [String? part2,
+        String? part3,
+        String? part4,
+        String? part5,
+        String? part6,
+        String? part7]) =>
     context.absolute(part1, part2, part3, part4, part5, part6, part7);
 
 /// Gets the part of [path] after the last separator.
@@ -255,13 +255,13 @@
 ///
 ///     p.join('path', '/to', 'foo'); // -> '/to/foo'
 String join(String part1,
-        [String part2,
-        String part3,
-        String part4,
-        String part5,
-        String part6,
-        String part7,
-        String part8]) =>
+        [String? part2,
+        String? part3,
+        String? part4,
+        String? part5,
+        String? part6,
+        String? part7,
+        String? part8]) =>
     context.join(part1, part2, part3, part4, part5, part6, part7, part8);
 
 /// Joins the given path parts into a single path using the current platform's
@@ -355,7 +355,7 @@
 ///     // URL
 ///     p.relative('https://dart.dev', from: 'https://pub.dev');
 ///       // -> 'https://dart.dev'
-String relative(String path, {String from}) =>
+String relative(String path, {String? from}) =>
     context.relative(path, from: from);
 
 /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise.
diff --git a/lib/src/context.dart b/lib/src/context.dart
index 21a7835..f0945e4 100644
--- a/lib/src/context.dart
+++ b/lib/src/context.dart
@@ -25,7 +25,7 @@
   ///
   /// On the browser, [style] defaults to [Style.url] and [current] defaults to
   /// the current URL.
-  factory Context({Style style, String current}) {
+  factory Context({Style? style, String? current}) {
     if (current == null) {
       if (style == null) {
         current = p.current;
@@ -56,7 +56,7 @@
 
   /// The current directory given when Context was created. If null, current
   /// directory is evaluated from 'p.current'.
-  final String _current;
+  final String? _current;
 
   /// The current directory that relative paths are relative to.
   String get current => _current ?? p.current;
@@ -75,12 +75,12 @@
   /// If [current] isn't absolute, this won't return an absolute path. Does not
   /// [normalize] or [canonicalize] paths.
   String absolute(String part1,
-      [String part2,
-      String part3,
-      String part4,
-      String part5,
-      String part6,
-      String part7]) {
+      [String? part2,
+      String? part3,
+      String? part4,
+      String? part5,
+      String? part6,
+      String? part7]) {
     _validateArgList(
         'absolute', [part1, part2, part3, part4, part5, part6, part7]);
 
@@ -222,14 +222,14 @@
   ///     context.join('path', '/to', 'foo'); // -> '/to/foo'
   ///
   String join(String part1,
-      [String part2,
-      String part3,
-      String part4,
-      String part5,
-      String part6,
-      String part7,
-      String part8]) {
-    final parts = <String>[
+      [String? part2,
+      String? part3,
+      String? part4,
+      String? part5,
+      String? part6,
+      String? part7,
+      String? part8]) {
+    final parts = <String?>[
       part1,
       part2,
       part3,
@@ -240,7 +240,7 @@
       part8
     ];
     _validateArgList('join', parts);
-    return joinAll(parts.where((part) => part != null));
+    return joinAll(parts.whereType<String>());
   }
 
   /// Joins the given path parts into a single path. Example:
@@ -270,7 +270,7 @@
         final path = buffer.toString();
         parsed.root =
             path.substring(0, style.rootLength(path, withDrive: true));
-        if (style.needsSeparator(parsed.root)) {
+        if (style.needsSeparator(parsed.root!)) {
           parsed.separators[0] = style.separator;
         }
         buffer.clear();
@@ -325,7 +325,7 @@
     final parsed = _parse(path);
     // Filter out empty parts that exist due to multiple separators in a row.
     parsed.parts = parsed.parts.where((part) => part.isNotEmpty).toList();
-    if (parsed.root != null) parsed.parts.insert(0, parsed.root);
+    if (parsed.root != null) parsed.parts.insert(0, parsed.root!);
     return parsed.parts;
   }
 
@@ -370,8 +370,8 @@
   bool _needsNormalization(String path) {
     var start = 0;
     final codeUnits = path.codeUnits;
-    int previousPrevious;
-    int previous;
+    int? previousPrevious;
+    int? previous;
 
     // Skip past the root before we start looking for snippets that need
     // normalization. We want to normalize "//", but not when it's part of
@@ -464,7 +464,7 @@
   /// [from] to [path]. For example, if [current] and [path] are "." and [from]
   /// is "/", no path can be determined. In this case, a [PathException] will be
   /// thrown.
-  String relative(String path, {String from}) {
+  String relative(String path, {String? from}) {
     // Avoid expensive computation if the path is already relative.
     if (from == null && isRelative(path)) return normalize(path);
 
@@ -500,7 +500,7 @@
     // calculation of relative paths, even if a path has not been normalized.
     if (fromParsed.root != pathParsed.root &&
         ((fromParsed.root == null || pathParsed.root == null) ||
-            !style.pathsEqual(fromParsed.root, pathParsed.root))) {
+            !style.pathsEqual(fromParsed.root!, pathParsed.root!))) {
       return pathParsed.toString();
     }
 
@@ -647,7 +647,7 @@
     var lastCodeUnit = chars.slash;
 
     /// The index of the last separator in [parent].
-    int lastParentSeparator;
+    int? lastParentSeparator;
 
     // Iterate through both paths as long as they're semantically identical.
     var parentIndex = parentRootLength;
@@ -765,8 +765,7 @@
         lastParentSeparator ??= math.max(0, parentRootLength - 1);
       }
 
-      final direction =
-          _pathDirection(parent, lastParentSeparator ?? parentRootLength - 1);
+      final direction = _pathDirection(parent, lastParentSeparator);
       if (direction == _PathDirection.atRoot) return _PathRelation.equal;
       return direction == _PathDirection.aboveRoot
           ? _PathRelation.inconclusive
@@ -888,14 +887,14 @@
 
     final parsed = _parse(path);
     parsed.normalize();
-    return _hashFast(parsed.toString());
+    return _hashFast(parsed.toString())!;
   }
 
   /// An optimized implementation of [hash] that doesn't handle internal `..`
   /// components.
   ///
   /// This will handle `..` components that appear at the beginning of the path.
-  int _hashFast(String path) {
+  int? _hashFast(String path) {
     var hash = 4603;
     var beginning = true;
     var wasSeparator = true;
@@ -1082,7 +1081,7 @@
 
 /// Validates that there are no non-null arguments following a null one and
 /// throws an appropriate [ArgumentError] on failure.
-void _validateArgList(String method, List<String> args) {
+void _validateArgList(String method, List<String?> args) {
   for (var i = 1; i < args.length; i++) {
     // Ignore nulls hanging off the end.
     if (args[i] == null || args[i - 1] != null) continue;
diff --git a/lib/src/internal_style.dart b/lib/src/internal_style.dart
index dbf9098..3041143 100644
--- a/lib/src/internal_style.dart
+++ b/lib/src/internal_style.dart
@@ -43,7 +43,7 @@
   /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
   /// returns `null`.
   @override
-  String getRoot(String path) {
+  String? getRoot(String path) {
     final length = rootLength(path);
     if (length > 0) return path.substring(0, length);
     return isRootRelative(path) ? path[0] : null;
diff --git a/lib/src/parsed_path.dart b/lib/src/parsed_path.dart
index 799a3bb..60fa849 100644
--- a/lib/src/parsed_path.dart
+++ b/lib/src/parsed_path.dart
@@ -13,7 +13,7 @@
   /// On POSIX systems, this will be `null` or "/". On Windows, it can be
   /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive
   /// letters.
-  String root;
+  String? root;
 
   /// Whether this path is root-relative.
   ///
@@ -33,7 +33,7 @@
 
   /// The file extension of the last non-empty part, or "" if it doesn't have
   /// one.
-  String extension([int level]) => _splitExtension(level)[1];
+  String extension([int level = 1]) => _splitExtension(level)[1];
 
   /// `true` if this is an absolute path.
   bool get isAbsolute => root != null;
@@ -131,14 +131,14 @@
     parts = newParts;
     separators =
         List.filled(newParts.length + 1, style.separator, growable: true);
-    if (!isAbsolute || newParts.isEmpty || !style.needsSeparator(root)) {
+    if (!isAbsolute || newParts.isEmpty || !style.needsSeparator(root!)) {
       separators[0] = '';
     }
 
     // Normalize the Windows root if needed.
     if (root != null && style == Style.windows) {
-      if (canonicalize) root = root.toLowerCase();
-      root = root.replaceAll('/', '\\');
+      if (canonicalize) root = root!.toLowerCase();
+      root = root!.replaceAll('/', '\\');
     }
     removeTrailingSeparators();
   }
@@ -185,13 +185,13 @@
   /// Returns a two-element list. The first is the name of the file without any
   /// extension. The second is the extension or "" if it has none.
   List<String> _splitExtension([int level = 1]) {
-    if (level == null) throw ArgumentError.notNull('level');
     if (level <= 0) {
       throw RangeError.value(
           level, 'level', "level's value must be greater than 0");
     }
 
-    final file = parts.lastWhere((p) => p != '', orElse: () => null);
+    final file =
+        parts.cast<String?>().lastWhere((p) => p != '', orElse: () => null);
 
     if (file == null) return ['', ''];
     if (file == '..') return ['..', ''];
diff --git a/lib/src/path_map.dart b/lib/src/path_map.dart
index 99bea34..50f4d7e 100644
--- a/lib/src/path_map.dart
+++ b/lib/src/path_map.dart
@@ -7,12 +7,12 @@
 import '../path.dart' as p;
 
 /// A map whose keys are paths, compared using [p.equals] and [p.hash].
-class PathMap<V> extends MapView<String, V> {
+class PathMap<V> extends MapView<String?, V> {
   /// Creates an empty [PathMap] whose keys are compared using `context.equals`
   /// and `context.hash`.
   ///
   /// The [context] defaults to the current path context.
-  PathMap({p.Context context}) : super(_create(context));
+  PathMap({p.Context? context}) : super(_create(context));
 
   /// Creates a [PathMap] with the same keys and values as [other] whose keys
   /// are compared using `context.equals` and `context.hash`.
@@ -20,19 +20,19 @@
   /// The [context] defaults to the current path context. If multiple keys in
   /// [other] represent the same logical path, the last key's value will be
   /// used.
-  PathMap.of(Map<String, V> other, {p.Context context})
+  PathMap.of(Map<String, V> other, {p.Context? context})
       : super(_create(context)..addAll(other));
 
   /// Creates a map that uses [context] for equality and hashing.
-  static Map<String, V> _create<V>(p.Context context) {
+  static Map<String?, V> _create<V>(p.Context? context) {
     context ??= p.context;
     return LinkedHashMap(
         equals: (path1, path2) {
           if (path1 == null) return path2 == null;
           if (path2 == null) return false;
-          return context.equals(path1, path2);
+          return context!.equals(path1, path2);
         },
-        hashCode: (path) => path == null ? 0 : context.hash(path),
+        hashCode: (path) => path == null ? 0 : context!.hash(path),
         isValidKey: (path) => path is String || path == null);
   }
 }
diff --git a/lib/src/path_set.dart b/lib/src/path_set.dart
index 9670239..424c8a1 100644
--- a/lib/src/path_set.dart
+++ b/lib/src/path_set.dart
@@ -7,15 +7,15 @@
 import '../path.dart' as p;
 
 /// A set containing paths, compared using [p.equals] and [p.hash].
-class PathSet extends IterableBase<String> implements Set<String> {
+class PathSet extends IterableBase<String?> implements Set<String?> {
   /// The set to which we forward implementation methods.
-  final Set<String> _inner;
+  final Set<String?> _inner;
 
   /// Creates an empty [PathSet] whose contents are compared using
   /// `context.equals` and `context.hash`.
   ///
   /// The [context] defaults to the current path context.
-  PathSet({p.Context context}) : _inner = _create(context);
+  PathSet({p.Context? context}) : _inner = _create(context);
 
   /// Creates a [PathSet] with the same contents as [other] whose elements are
   /// compared using `context.equals` and `context.hash`.
@@ -23,19 +23,19 @@
   /// The [context] defaults to the current path context. If multiple elements
   /// in [other] represent the same logical path, the first value will be
   /// used.
-  PathSet.of(Iterable<String> other, {p.Context context})
+  PathSet.of(Iterable<String> other, {p.Context? context})
       : _inner = _create(context)..addAll(other);
 
   /// Creates a set that uses [context] for equality and hashing.
-  static Set<String> _create(p.Context context) {
+  static Set<String?> _create(p.Context? context) {
     context ??= p.context;
     return LinkedHashSet(
         equals: (path1, path2) {
           if (path1 == null) return path2 == null;
           if (path2 == null) return false;
-          return context.equals(path1, path2);
+          return context!.equals(path1, path2);
         },
-        hashCode: (path) => path == null ? 0 : context.hash(path),
+        hashCode: (path) => path == null ? 0 : context!.hash(path),
         isValidKey: (path) => path is String || path == null);
   }
 
@@ -44,16 +44,16 @@
   // it's so widely used that even brief version skew can be very painful.
 
   @override
-  Iterator<String> get iterator => _inner.iterator;
+  Iterator<String?> get iterator => _inner.iterator;
 
   @override
   int get length => _inner.length;
 
   @override
-  bool add(String value) => _inner.add(value);
+  bool add(String? value) => _inner.add(value);
 
   @override
-  void addAll(Iterable<String> elements) => _inner.addAll(elements);
+  void addAll(Iterable<String?> elements) => _inner.addAll(elements);
 
   @override
   Set<T> cast<T>() => _inner.cast<T>();
@@ -62,38 +62,38 @@
   void clear() => _inner.clear();
 
   @override
-  bool contains(Object element) => _inner.contains(element);
+  bool contains(Object? element) => _inner.contains(element);
 
   @override
-  bool containsAll(Iterable<Object> other) => _inner.containsAll(other);
+  bool containsAll(Iterable<Object?> other) => _inner.containsAll(other);
 
   @override
-  Set<String> difference(Set<Object> other) => _inner.difference(other);
+  Set<String?> difference(Set<Object?> other) => _inner.difference(other);
 
   @override
-  Set<String> intersection(Set<Object> other) => _inner.intersection(other);
+  Set<String?> intersection(Set<Object?> other) => _inner.intersection(other);
 
   @override
-  String lookup(Object element) => _inner.lookup(element);
+  String? lookup(Object? element) => _inner.lookup(element);
 
   @override
-  bool remove(Object value) => _inner.remove(value);
+  bool remove(Object? value) => _inner.remove(value);
 
   @override
-  void removeAll(Iterable<Object> elements) => _inner.removeAll(elements);
+  void removeAll(Iterable<Object?> elements) => _inner.removeAll(elements);
 
   @override
-  void removeWhere(bool Function(String) test) => _inner.removeWhere(test);
+  void removeWhere(bool Function(String?) test) => _inner.removeWhere(test);
 
   @override
-  void retainAll(Iterable<Object> elements) => _inner.retainAll(elements);
+  void retainAll(Iterable<Object?> elements) => _inner.retainAll(elements);
 
   @override
-  void retainWhere(bool Function(String) test) => _inner.retainWhere(test);
+  void retainWhere(bool Function(String?) test) => _inner.retainWhere(test);
 
   @override
-  Set<String> union(Set<String> other) => _inner.union(other);
+  Set<String?> union(Set<String?> other) => _inner.union(other);
 
   @override
-  Set<String> toSet() => _inner.toSet();
+  Set<String?> toSet() => _inner.toSet();
 }
diff --git a/lib/src/style.dart b/lib/src/style.dart
index d8b8ff1..e1b4fec 100644
--- a/lib/src/style.dart
+++ b/lib/src/style.dart
@@ -63,13 +63,13 @@
   Pattern get rootPattern;
 
   @Deprecated('Most Style members will be removed in path 2.0.')
-  Pattern get relativeRootPattern;
+  Pattern? get relativeRootPattern;
 
   @Deprecated('Most style members will be removed in path 2.0.')
-  String getRoot(String path);
+  String? getRoot(String path);
 
   @Deprecated('Most style members will be removed in path 2.0.')
-  String getRelativeRoot(String path);
+  String? getRelativeRoot(String path);
 
   @Deprecated('Most style members will be removed in path 2.0.')
   String pathFromUri(Uri uri);
diff --git a/lib/src/style/posix.dart b/lib/src/style/posix.dart
index f8b7e78..59c8d32 100644
--- a/lib/src/style/posix.dart
+++ b/lib/src/style/posix.dart
@@ -47,7 +47,7 @@
   bool isRootRelative(String path) => false;
 
   @override
-  String getRelativeRoot(String path) => null;
+  String? getRelativeRoot(String path) => null;
 
   @override
   String pathFromUri(Uri uri) {
diff --git a/lib/src/style/url.dart b/lib/src/style/url.dart
index 1f99bd5..9daccd7 100644
--- a/lib/src/style/url.dart
+++ b/lib/src/style/url.dart
@@ -79,7 +79,7 @@
       path.isNotEmpty && isSeparator(path.codeUnitAt(0));
 
   @override
-  String getRelativeRoot(String path) => isRootRelative(path) ? '/' : null;
+  String? getRelativeRoot(String path) => isRootRelative(path) ? '/' : null;
 
   @override
   String pathFromUri(Uri uri) => uri.toString();
diff --git a/lib/src/style/windows.dart b/lib/src/style/windows.dart
index 64d5a3f..3b7d98c 100644
--- a/lib/src/style/windows.dart
+++ b/lib/src/style/windows.dart
@@ -76,7 +76,7 @@
   bool isRootRelative(String path) => rootLength(path) == 1;
 
   @override
-  String getRelativeRoot(String path) {
+  String? getRelativeRoot(String path) {
     final length = rootLength(path);
     if (length == 1) return path[0];
     return null;
@@ -106,12 +106,12 @@
   @override
   Uri absolutePathToUri(String path) {
     final parsed = ParsedPath.parse(path, this);
-    if (parsed.root.startsWith(r'\\')) {
+    if (parsed.root!.startsWith(r'\\')) {
       // Network paths become "file://server/share/path/to/file".
 
       // The root is of the form "\\server\share". We want "server" to be the
       // URI host, and "share" to be the first element of the path.
-      final rootParts = parsed.root.split('\\').where((part) => part != '');
+      final rootParts = parsed.root!.split('\\').where((part) => part != '');
       parsed.parts.insert(0, rootParts.last);
 
       if (parsed.hasTrailingSeparator) {
@@ -136,7 +136,7 @@
       // Get rid of the trailing "\" in "C:\" because the URI constructor will
       // add a separator on its own.
       parsed.parts
-          .insert(0, parsed.root.replaceAll('/', '').replaceAll('\\', ''));
+          .insert(0, parsed.root!.replaceAll('/', '').replaceAll('\\', ''));
 
       return Uri(scheme: 'file', pathSegments: parsed.parts);
     }
diff --git a/pubspec.yaml b/pubspec.yaml
index 92a54f9..e25589c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: path
-version: 1.7.1-dev
+version: 1.8.0-nullsafety
 
 description: >-
   A string-based path manipulation library. All of the path operations you know
@@ -8,8 +8,88 @@
 homepage: https://github.com/dart-lang/path
 
 environment:
- sdk: '>=2.0.0 <3.0.0'
+ sdk: '>=2.9.0-18.0 <2.9.0'
 
 dev_dependencies:
   pedantic: ^1.0.0
   test: '>=0.12.42 <2.0.0'
+
+  build_runner: ^1.0.0
+  build_test: ^0.10.0
+  build_web_compilers: '>=1.2.0 <3.0.0'
+
+dependency_overrides:
+  async:
+    git:
+      url: git://github.com/dart-lang/async.git
+      ref: null_safety
+  boolean_selector:
+    git:
+      url: git://github.com/dart-lang/boolean_selector.git
+      ref: null_safety
+  charcode:
+    git:
+      url: git://github.com/dart-lang/charcode.git
+      ref: null_safety
+  collection: 1.15.0-nullsafety
+  js:
+    git:
+      url: git://github.com/dart-lang/sdk.git
+      ref: master
+      path: pkg/js
+  matcher:
+    git:
+      url: git://github.com/dart-lang/matcher.git
+      ref: null_safety
+  meta: 1.3.0-nullsafety
+  pedantic:
+    git:
+      url: git://github.com/dart-lang/pedantic.git
+      ref: null_safety
+  pool:
+    git:
+      url: git://github.com/dart-lang/pool.git
+      ref: null_safety
+  source_maps:
+    git:
+      url: git://github.com/dart-lang/source_maps.git
+      ref: null_safety
+  source_map_stack_trace:
+    git:
+      url: git://github.com/dart-lang/source_map_stack_trace.git
+      ref: null_safety
+  source_span:
+    git:
+      url: git://github.com/dart-lang/source_span.git
+      ref: null_safety
+  stack_trace:
+    git:
+      url: git://github.com/dart-lang/stack_trace.git
+      ref: null_safety
+  stream_channel:
+    git:
+      url: git://github.com/dart-lang/stream_channel.git
+      ref: null_safety
+  string_scanner:
+    git:
+      url: git://github.com/dart-lang/string_scanner.git
+      ref: null_safety
+  term_glyph:
+    git:
+      url: git://github.com/dart-lang/term_glyph.git
+      ref: null_safety
+  test_api:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test_api
+  test_core:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test_core
+  test:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test
diff --git a/test/posix_test.dart b/test/posix_test.dart
index 6b73145..8ade403 100644
--- a/test/posix_test.dart
+++ b/test/posix_test.dart
@@ -32,8 +32,6 @@
     expect(context.extension('a.b/c.d', 2), '.d');
     expect(() => context.extension(r'foo.bar.dart.js', 0), throwsRangeError);
     expect(() => context.extension(r'foo.bar.dart.js', -1), throwsRangeError);
-    expect(
-        () => context.extension(r'foo.bar.dart.js', null), throwsArgumentError);
   });
 
   test('rootPrefix', () {
@@ -183,7 +181,6 @@
 
     test('disallows intermediate nulls', () {
       expect(() => context.join('a', null, 'b'), throwsArgumentError);
-      expect(() => context.join(null, 'a'), throwsArgumentError);
     });
 
     test('join does not modify internal ., .., or trailing separators', () {
diff --git a/test/url_test.dart b/test/url_test.dart
index 143b602..cb2d4cb 100644
--- a/test/url_test.dart
+++ b/test/url_test.dart
@@ -253,7 +253,6 @@
 
     test('disallows intermediate nulls', () {
       expect(() => context.join('a', null, 'b'), throwsArgumentError);
-      expect(() => context.join(null, 'a'), throwsArgumentError);
     });
 
     test('does not modify internal ., .., or trailing separators', () {
diff --git a/test/windows_test.dart b/test/windows_test.dart
index ad3deb6..d658795 100644
--- a/test/windows_test.dart
+++ b/test/windows_test.dart
@@ -36,8 +36,6 @@
     expect(context.extension('a.b/c.d', 2), '.d');
     expect(() => context.extension(r'foo.bar.dart.js', 0), throwsRangeError);
     expect(() => context.extension(r'foo.bar.dart.js', -1), throwsRangeError);
-    expect(
-        () => context.extension(r'foo.bar.dart.js', null), throwsArgumentError);
   });
 
   test('rootPrefix', () {
@@ -248,7 +246,6 @@
 
     test('disallows intermediate nulls', () {
       expect(() => context.join('a', null, 'b'), throwsArgumentError);
-      expect(() => context.join(null, 'a'), throwsArgumentError);
     });
 
     test('join does not modify internal ., .., or trailing separators', () {