Add a Watcher interface.

Unlike DirectoryWatcher, this interface isn't directory-specific. This
also allows ResubscribableDirectoryWatcher to become
ResubscribableWatcher, which will make it easier to implement
single-file watching.

See dart-lang/watcher#17

R=rnystrom@google.com

Review URL: https://codereview.chromium.org//1180233003.
diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md
index 7772980..f8c54bf 100644
--- a/pkgs/watcher/CHANGELOG.md
+++ b/pkgs/watcher/CHANGELOG.md
@@ -1,3 +1,10 @@
+# 0.9.6
+
+* Add a `Watcher` interface that encompasses watching both files and
+  directories.
+
+* Deprecate `DirectoryWatcher.directory`. Use `DirectoryWatcher.path` instead.
+
 # 0.9.5
 
 * Fix bugs where events could be added after watchers were closed.
diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart
index 605eaea..6979536 100644
--- a/pkgs/watcher/lib/src/directory_watcher.dart
+++ b/pkgs/watcher/lib/src/directory_watcher.dart
@@ -4,10 +4,10 @@
 
 library watcher.directory_watcher;
 
-import 'dart:async';
 import 'dart:io';
 
 import 'watch_event.dart';
+import '../watcher.dart';
 import 'directory_watcher/linux.dart';
 import 'directory_watcher/mac_os.dart';
 import 'directory_watcher/windows.dart';
@@ -15,35 +15,11 @@
 
 /// Watches the contents of a directory and emits [WatchEvent]s when something
 /// in the directory has changed.
-abstract class DirectoryWatcher {
+abstract class DirectoryWatcher implements Watcher {
   /// The directory whose contents are being monitored.
+  @Deprecated("Expires in 1.0.0. Use DirectoryWatcher.path instead.")
   String get directory;
 
-  /// The broadcast [Stream] of events that have occurred to files in
-  /// [directory].
-  ///
-  /// Changes will only be monitored while this stream has subscribers. Any
-  /// file changes that occur during periods when there are no subscribers
-  /// will not be reported the next time a subscriber is added.
-  Stream<WatchEvent> get events;
-
-  /// Whether the watcher is initialized and watching for file changes.
-  ///
-  /// This is true if and only if [ready] is complete.
-  bool get isReady;
-
-  /// A [Future] that completes when the watcher is initialized and watching
-  /// for file changes.
-  ///
-  /// If the watcher is not currently monitoring the directory (because there
-  /// are no subscribers to [events]), this returns a future that isn't
-  /// complete yet. It will complete when a subscriber starts listening and
-  /// the watcher finishes any initialization work it needs to do.
-  ///
-  /// If the watcher is already monitoring, this returns an already complete
-  /// future.
-  Future get ready;
-
   /// Creates a new [DirectoryWatcher] monitoring [directory].
   ///
   /// If a native directory watcher is available for this platform, this will
diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart
index 04d88e6..a747839 100644
--- a/pkgs/watcher/lib/src/directory_watcher/linux.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart
@@ -7,9 +7,10 @@
 import 'dart:async';
 import 'dart:io';
 
+import '../directory_watcher.dart';
+import '../resubscribable.dart';
 import '../utils.dart';
 import '../watch_event.dart';
-import 'resubscribable.dart';
 
 /// Uses the inotify subsystem to watch for filesystem events.
 ///
@@ -21,13 +22,18 @@
 /// [Directory.watch] producing multiple events for a single logical action
 /// (issue 14372) and providing insufficient information about move events
 /// (issue 14424).
-class LinuxDirectoryWatcher extends ResubscribableDirectoryWatcher {
+class LinuxDirectoryWatcher extends ResubscribableWatcher
+    implements DirectoryWatcher {
+  String get directory => path;
+
   LinuxDirectoryWatcher(String directory)
       : super(directory, () => new _LinuxDirectoryWatcher(directory));
 }
 
-class _LinuxDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
-  final String directory;
+class _LinuxDirectoryWatcher
+    implements DirectoryWatcher, ManuallyClosedWatcher {
+  String get directory => path;
+  final String path;
 
   Stream<WatchEvent> get events => _eventsController.stream;
   final _eventsController = new StreamController<WatchEvent>.broadcast();
@@ -53,16 +59,15 @@
   /// watcher is closed.
   final _subscriptions = new Set<StreamSubscription>();
 
-  _LinuxDirectoryWatcher(String directory)
-      : directory = directory {
+  _LinuxDirectoryWatcher(this.path) {
     // Batch the inotify changes together so that we can dedup events.
-    var innerStream = new Directory(directory).watch()
+    var innerStream = new Directory(path).watch()
         .transform(new BatchedStreamTransformer<FileSystemEvent>());
     _listen(innerStream, _onBatch,
         onError: _eventsController.addError,
         onDone: _onDone);
 
-    _listen(new Directory(directory).list(), (entity) {
+    _listen(new Directory(path).list(), (entity) {
       _entries[entity.path] = new _EntryState(entity is Directory);
       if (entity is! Directory) return;
       _watchSubdir(entity.path);
@@ -182,7 +187,7 @@
       // If the watched directory is deleted or moved, we'll get a deletion
       // event for it. Ignore it; we handle closing [this] when the underlying
       // stream is closed.
-      if (event.path == directory) continue;
+      if (event.path == path) continue;
 
       changedEntries.add(event.path);
 
diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
index f3de727..487225e 100644
--- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
@@ -7,11 +7,12 @@
 import 'dart:async';
 import 'dart:io';
 
+import '../directory_watcher.dart';
 import '../constructable_file_system_event.dart';
 import '../path_set.dart';
+import '../resubscribable.dart';
 import '../utils.dart';
 import '../watch_event.dart';
-import 'resubscribable.dart';
 
 /// Uses the FSEvents subsystem to watch for filesystem events.
 ///
@@ -23,13 +24,18 @@
 ///
 /// This also works around issues 16003 and 14849 in the implementation of
 /// [Directory.watch].
-class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher {
+class MacOSDirectoryWatcher extends ResubscribableWatcher
+    implements DirectoryWatcher {
+  String get directory => path;
+
   MacOSDirectoryWatcher(String directory)
       : super(directory, () => new _MacOSDirectoryWatcher(directory));
 }
 
-class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
-  final String directory;
+class _MacOSDirectoryWatcher
+    implements DirectoryWatcher, ManuallyClosedWatcher {
+  String get directory => path;
+  final String path;
 
   Stream<WatchEvent> get events => _eventsController.stream;
   final _eventsController = new StreamController<WatchEvent>.broadcast();
@@ -66,9 +72,9 @@
   /// events (see issue 14373).
   Timer _bogusEventTimer;
 
-  _MacOSDirectoryWatcher(String directory)
-      : directory = directory,
-        _files = new PathSet(directory) {
+  _MacOSDirectoryWatcher(String path)
+      : path = path,
+        _files = new PathSet(path) {
     _startWatch();
 
     // Before we're ready to emit events, wait for [_listDir] to complete and
@@ -167,14 +173,14 @@
   /// CREATE event for the destination.
   ///
   /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it
-  /// contain any events relating to [directory].
+  /// contain any events relating to [path].
   Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) {
     var eventsForPaths = {};
 
     // FSEvents can report past events, including events on the root directory
     // such as it being created. We want to ignore these. If the directory is
     // really deleted, that's handled by [_onDone].
-    batch = batch.where((event) => event.path != directory).toList();
+    batch = batch.where((event) => event.path != path).toList();
 
     // Events within directories that already have events are superfluous; the
     // directory's full contents will be examined anyway, so we ignore such
@@ -323,7 +329,7 @@
     // If the directory still exists and we're still expecting bogus events,
     // this is probably issue 14849 rather than a real close event. We should
     // just restart the watcher.
-    if (!isReady && new Directory(directory).existsSync()) {
+    if (!isReady && new Directory(path).existsSync()) {
       _startWatch();
       return;
     }
@@ -341,7 +347,7 @@
   /// Start or restart the underlying [Directory.watch] stream.
   void _startWatch() {
     // Batch the FSEvent changes together so that we can dedup events.
-    var innerStream = new Directory(directory).watch(recursive: true)
+    var innerStream = new Directory(path).watch(recursive: true)
         .transform(new BatchedStreamTransformer<FileSystemEvent>());
     _watchSubscription = innerStream.listen(_onBatch,
         onError: _eventsController.addError,
@@ -356,7 +362,7 @@
 
     _files.clear();
     var completer = new Completer();
-    var stream = new Directory(directory).list(recursive: true);
+    var stream = new Directory(path).list(recursive: true);
     _initialListSubscription = stream.listen((entity) {
       if (entity is! Directory) _files.add(entity.path);
     },
diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart
index 144391f..7f417d6 100644
--- a/pkgs/watcher/lib/src/directory_watcher/polling.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart
@@ -8,13 +8,17 @@
 import 'dart:io';
 
 import '../async_queue.dart';
+import '../directory_watcher.dart';
+import '../resubscribable.dart';
 import '../stat.dart';
 import '../utils.dart';
 import '../watch_event.dart';
-import 'resubscribable.dart';
 
 /// Periodically polls a directory for changes.
-class PollingDirectoryWatcher extends ResubscribableDirectoryWatcher {
+class PollingDirectoryWatcher extends ResubscribableWatcher
+    implements DirectoryWatcher {
+  String get directory => path;
+
   /// Creates a new polling watcher monitoring [directory].
   ///
   /// If [_pollingDelay] is passed, it specifies the amount of time the watcher
@@ -28,8 +32,10 @@
       });
 }
 
-class _PollingDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
-  final String directory;
+class _PollingDirectoryWatcher
+    implements DirectoryWatcher, ManuallyClosedWatcher {
+  String get directory => path;
+  final String path;
 
   Stream<WatchEvent> get events => _events.stream;
   final _events = new StreamController<WatchEvent>.broadcast();
@@ -68,7 +74,7 @@
   /// but not in here when a poll completes have been removed.
   final _polledFiles = new Set<String>();
 
-  _PollingDirectoryWatcher(this.directory, this._pollingDelay) {
+  _PollingDirectoryWatcher(this.path, this._pollingDelay) {
     _filesToProcess = new AsyncQueue<String>(_processFile,
         onError: (e, stackTrace) {
       if (!_events.isClosed) _events.addError(e, stackTrace);
@@ -103,7 +109,7 @@
       _filesToProcess.add(null);
     }
 
-    var stream = new Directory(directory).list(recursive: true);
+    var stream = new Directory(path).list(recursive: true);
     _listSubscription = stream.listen((entity) {
       assert(!_events.isClosed);
 
diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart
index bd17cf4..0899519 100644
--- a/pkgs/watcher/lib/src/directory_watcher/windows.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart
@@ -12,12 +12,16 @@
 import 'package:path/path.dart' as p;

 

 import '../constructable_file_system_event.dart';

+import '../directory_watcher.dart';

 import '../path_set.dart';

+import '../resubscribable.dart';

 import '../utils.dart';

 import '../watch_event.dart';

-import 'resubscribable.dart';

 

-class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher {

+class WindowsDirectoryWatcher extends ResubscribableWatcher

+    implements DirectoryWatcher {

+  String get directory => path;

+

   WindowsDirectoryWatcher(String directory)

       : super(directory, () => new _WindowsDirectoryWatcher(directory));

 }

@@ -40,8 +44,10 @@
   }

 }

 

-class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher {

-  final String directory;

+class _WindowsDirectoryWatcher

+    implements DirectoryWatcher, ManuallyClosedWatcher {

+  String get directory => path;

+  final String path;

 

   Stream<WatchEvent> get events => _eventsController.stream;

   final _eventsController = new StreamController<WatchEvent>.broadcast();

@@ -79,8 +85,9 @@
   final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions

       = new HashSet<StreamSubscription<FileSystemEntity>>();

 

-  _WindowsDirectoryWatcher(String directory)

-      : directory = directory, _files = new PathSet(directory) {

+  _WindowsDirectoryWatcher(String path)

+      : path = path,

+        _files = new PathSet(path) {

     // Before we're ready to emit events, wait for [_listDir] to complete.

     _listDir().then((_) {

       _startWatch();

@@ -110,12 +117,12 @@
   /// On Windows, if [directory] is deleted, we will not receive any event.

   ///

   /// Instead, we add a watcher on the parent folder (if any), that can notify

-  /// us about [directory]. This also includes events such as moves.

+  /// us about [path]. This also includes events such as moves.

   void _startParentWatcher() {

-    var absoluteDir = p.absolute(directory);

+    var absoluteDir = p.absolute(path);

     var parent = p.dirname(absoluteDir);

-    // Check if [directory] is already the root directory.

-    if (FileSystemEntity.identicalSync(parent, directory)) return;

+    // Check if [path] is already the root directory.

+    if (FileSystemEntity.identicalSync(parent, path)) return;

     var parentStream = new Directory(parent).watch(recursive: false);

     _parentWatchSubscription = parentStream.listen((event) {

       // Only look at events for 'directory'.

@@ -127,7 +134,7 @@
       // the directory is now gone.

       if (event is FileSystemMoveEvent ||

           event is FileSystemDeleteEvent ||

-          (FileSystemEntity.typeSync(directory) ==

+          (FileSystemEntity.typeSync(path) ==

            FileSystemEntityType.NOT_FOUND)) {

         for (var path in _files.toSet()) {

           _emitEvent(ChangeType.REMOVE, path);

@@ -210,7 +217,7 @@
   /// CREATE event for the destination.

   ///

   /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it

-  /// contain any events relating to [directory].

+  /// contain any events relating to [path].

   Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) {

     var eventsForPaths = {};

 

@@ -360,7 +367,7 @@
   /// Start or restart the underlying [Directory.watch] stream.

   void _startWatch() {

     // Batch the events together so that we can dedup events.

-    var innerStream = new Directory(directory).watch(recursive: true);

+    var innerStream = new Directory(path).watch(recursive: true);

     _watchSubscription = innerStream.listen(_onEvent,

         onError: _eventsController.addError,

         onDone: _onDone);

@@ -374,7 +381,7 @@
 

     _files.clear();

     var completer = new Completer();

-    var stream = new Directory(directory).list(recursive: true);

+    var stream = new Directory(path).list(recursive: true);

     void handleEntity(entity) {

       if (entity is! Directory) _files.add(entity.path);

     }

diff --git a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart
similarity index 74%
rename from pkgs/watcher/lib/src/directory_watcher/resubscribable.dart
rename to pkgs/watcher/lib/src/resubscribable.dart
index 7d99fc0..2844c1e 100644
--- a/pkgs/watcher/lib/src/directory_watcher/resubscribable.dart
+++ b/pkgs/watcher/lib/src/resubscribable.dart
@@ -2,33 +2,33 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library watcher.directory_watcher.resubscribable;
+library watcher.resubscribable;
 
 import 'dart:async';
 
-import '../directory_watcher.dart';
-import '../watch_event.dart';
+import '../watcher.dart';
+import 'watch_event.dart';
 
-typedef ManuallyClosedDirectoryWatcher WatcherFactory();
+typedef ManuallyClosedWatcher WatcherFactory();
 
-/// A wrapper for [ManuallyClosedDirectoryWatcher] that encapsulates support for
-/// closing the watcher when it has no subscribers and re-opening it when it's
+/// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing
+/// the watcher when it has no subscribers and re-opening it when it's
 /// re-subscribed.
 ///
 /// It's simpler to implement watchers without worrying about this behavior.
 /// This class wraps a watcher class which can be written with the simplifying
 /// assumption that it can continue emitting events until an explicit `close`
 /// method is called, at which point it will cease emitting events entirely. The
-/// [ManuallyClosedDirectoryWatcher] interface is used for these watchers.
+/// [ManuallyClosedWatcher] interface is used for these watchers.
 ///
 /// This would be more cleanly implemented as a function that takes a class and
 /// emits a new class, but Dart doesn't support that sort of thing. Instead it
 /// takes a factory function that produces instances of the inner class.
-abstract class ResubscribableDirectoryWatcher implements DirectoryWatcher {
+abstract class ResubscribableWatcher implements Watcher {
   /// The factory function that produces instances of the inner class.
   final WatcherFactory _factory;
 
-  final String directory;
+  final String path;
 
   Stream<WatchEvent> get events => _eventsController.stream;
   StreamController<WatchEvent> _eventsController;
@@ -38,9 +38,9 @@
   Future get ready => _readyCompleter.future;
   var _readyCompleter = new Completer();
 
-  /// Creates a new [ResubscribableDirectoryWatcher] wrapping the watchers
+  /// Creates a new [ResubscribableWatcher] wrapping the watchers
   /// emitted by [_factory].
-  ResubscribableDirectoryWatcher(this.directory, this._factory) {
+  ResubscribableWatcher(this.path, this._factory) {
     var watcher;
     var subscription;
 
@@ -67,8 +67,8 @@
 
 /// An interface for watchers with an explicit, manual [close] method.
 ///
-/// See [ResubscribableDirectoryWatcher].
-abstract class ManuallyClosedDirectoryWatcher implements DirectoryWatcher {
+/// See [ResubscribableWatcher].
+abstract class ManuallyClosedWatcher implements Watcher {
   /// Closes the watcher.
   ///
   /// Subclasses should close their [events] stream and release any internal
diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart
index 88531f2..a078058 100644
--- a/pkgs/watcher/lib/watcher.dart
+++ b/pkgs/watcher/lib/watcher.dart
@@ -4,6 +4,40 @@
 
 library watcher;
 
+import 'dart:async';
+
+import 'src/watch_event.dart';
+
 export 'src/watch_event.dart';
 export 'src/directory_watcher.dart';
 export 'src/directory_watcher/polling.dart';
+
+abstract class Watcher {
+  /// The path to the file or directory whose contents are being monitored.
+  String get path;
+
+  /// The broadcast [Stream] of events that have occurred to the watched file or
+  /// files in the watched directory.
+  ///
+  /// Changes will only be monitored while this stream has subscribers. Any
+  /// changes that occur during periods when there are no subscribers will not
+  /// be reported the next time a subscriber is added.
+  Stream<WatchEvent> get events;
+
+  /// Whether the watcher is initialized and watching for changes.
+  ///
+  /// This is true if and only if [ready] is complete.
+  bool get isReady;
+
+  /// A [Future] that completes when the watcher is initialized and watching for
+  /// changes.
+  ///
+  /// If the watcher is not currently monitoring the file or directory (because
+  /// there are no subscribers to [events]), this returns a future that isn't
+  /// complete yet. It will complete when a subscriber starts listening and the
+  /// watcher finishes any initialization work it needs to do.
+  ///
+  /// If the watcher is already monitoring, this returns an already complete
+  /// future.
+  Future get ready;
+}
diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml
index 08695c9..8853995 100644
--- a/pkgs/watcher/pubspec.yaml
+++ b/pkgs/watcher/pubspec.yaml
@@ -9,7 +9,6 @@
   sdk: '>=1.8.0 <2.0.0'
 dependencies:
   path: '>=0.9.0 <2.0.0'
-  stack_trace: '>=0.9.1 <2.0.0'
 dev_dependencies:
   scheduled_test: '^0.12.0'
   test: '^0.12.0'
diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart
index 15eb0ce..897b130 100644
--- a/pkgs/watcher/test/directory_watcher/linux_test.dart
+++ b/pkgs/watcher/test/directory_watcher/linux_test.dart
@@ -27,7 +27,7 @@
       () {
     withPermutations((i, j, k) =>
         writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt"));
-    startWatcher(dir: "dir");
+    startWatcher(path: "dir");
 
     renameDir("dir/sub", "sub");
     renameDir("sub", "dir/sub");
diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart
index e9a1696..2c82f34 100644
--- a/pkgs/watcher/test/directory_watcher/mac_os_test.dart
+++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart
@@ -30,7 +30,7 @@
     deleteDir("dir");
     createDir("dir");
 
-    startWatcher(dir: "dir");
+    startWatcher(path: "dir");
     writeFile("dir/newer.txt");
     expectAddEvent("dir/newer.txt");
   });
@@ -40,7 +40,7 @@
     withPermutations((i, j, k) =>
         writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt"));
 
-    startWatcher(dir: "dir");
+    startWatcher(path: "dir");
 
     renameDir("dir/sub", "sub");
     renameDir("sub", "dir/sub");
diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart
index a4cec5e..ff48cb2 100644
--- a/pkgs/watcher/test/directory_watcher/shared.dart
+++ b/pkgs/watcher/test/directory_watcher/shared.dart
@@ -69,7 +69,7 @@
     writeFile("dir/a.txt");
     writeFile("dir/b.txt");
 
-    startWatcher(dir: "dir");
+    startWatcher(path: "dir");
 
     deleteDir("dir");
     inAnyOrder([
@@ -82,7 +82,7 @@
     writeFile("dir/a.txt");
     writeFile("dir/b.txt");
 
-    startWatcher(dir: "dir");
+    startWatcher(path: "dir");
 
     renameDir("dir", "moved_dir");
     createDir("dir");
@@ -108,7 +108,7 @@
         () {
       writeFile("old.txt");
       createDir("dir");
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
 
       renameFile("old.txt", "dir/new.txt");
       expectAddEvent("dir/new.txt");
@@ -116,7 +116,7 @@
 
     test('notifies when a file is moved outside the watched directory', () {
       writeFile("dir/old.txt");
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
 
       renameFile("dir/old.txt", "new.txt");
       expectRemoveEvent("dir/old.txt");
@@ -251,7 +251,7 @@
           writeFile("sub/sub-$i/sub-$j/file-$k.txt"));
 
       createDir("dir");
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
       renameDir("sub", "dir/sub");
 
       inAnyOrder(withPermutations((i, j, k)  =>
@@ -263,7 +263,7 @@
           writeFile("dir/sub/sub-$i/sub-$j/file-$k.txt"));
 
       createDir("dir");
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
 
       // Rename the directory rather than deleting it because native watchers
       // report a rename as a single DELETE event for the directory, whereas
@@ -280,7 +280,7 @@
           writeFile("dir/old/sub-$i/sub-$j/file-$k.txt"));
 
       createDir("dir");
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
       renameDir("dir/old", "dir/new");
 
       inAnyOrder(unionAll(withPermutations((i, j, k) {
@@ -296,7 +296,7 @@
       writeFile("dir/sub");
       withPermutations((i, j, k) =>
           writeFile("old/sub-$i/sub-$j/file-$k.txt"));
-      startWatcher(dir: "dir");
+      startWatcher(path: "dir");
 
       deleteFile("dir/sub");
       renameDir("old", "dir/sub");
diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart
index 85fe6d3..7dd8332 100644
--- a/pkgs/watcher/test/utils.dart
+++ b/pkgs/watcher/test/utils.dart
@@ -20,8 +20,8 @@
 /// operations are implicitly relative to this directory.
 String _sandboxDir;
 
-/// The [DirectoryWatcher] being used for the current scheduled test.
-DirectoryWatcher _watcher;
+/// The [Watcher] being used for the current scheduled test.
+Watcher _watcher;
 
 /// The mock modification times (in milliseconds since epoch) for each file.
 ///
@@ -34,9 +34,9 @@
 /// increment the mod time for that file instantly.
 Map<String, int> _mockFileModificationTimes;
 
-typedef DirectoryWatcher WatcherFactory(String directory);
+typedef Watcher WatcherFactory(String directory);
 
-/// Sets the function used to create the directory watcher.
+/// Sets the function used to create the watcher.
 set watcherFactory(WatcherFactory factory) {
   _watcherFactory = factory;
 }
@@ -78,21 +78,21 @@
   }, "delete sandbox");
 }
 
-/// Creates a new [DirectoryWatcher] that watches a temporary directory.
+/// Creates a new [Watcher] that watches a temporary file or directory.
 ///
 /// Normally, this will pause the schedule until the watcher is done scanning
 /// and is polling for changes. If you pass `false` for [waitForReady], it will
 /// not schedule this delay.
 ///
-/// If [dir] is provided, watches a subdirectory in the sandbox with that name.
-DirectoryWatcher createWatcher({String dir, bool waitForReady}) {
-  if (dir == null) {
-    dir = _sandboxDir;
+/// If [path] is provided, watches a subdirectory in the sandbox with that name.
+Watcher createWatcher({String path, bool waitForReady}) {
+  if (path == null) {
+    path = _sandboxDir;
   } else {
-    dir = p.join(_sandboxDir, dir);
+    path = p.join(_sandboxDir, path);
   }
 
-  var watcher = _watcherFactory(dir);
+  var watcher = _watcherFactory(path);
 
   // Wait until the scan is finished so that we don't miss changes to files
   // that could occur before the scan completes.
@@ -106,17 +106,17 @@
 /// The stream of events from the watcher started with [startWatcher].
 ScheduledStream<WatchEvent> _watcherEvents;
 
-/// Creates a new [DirectoryWatcher] that watches a temporary directory and
+/// Creates a new [Watcher] that watches a temporary file or directory and
 /// starts monitoring it for events.
 ///
-/// If [dir] is provided, watches a subdirectory in the sandbox with that name.
-void startWatcher({String dir}) {
+/// If [path] is provided, watches a path in the sandbox with that name.
+void startWatcher({String path}) {
   // We want to wait until we're ready *after* we subscribe to the watcher's
   // events.
-  _watcher = createWatcher(dir: dir, waitForReady: false);
+  _watcher = createWatcher(path: path, waitForReady: false);
 
   // Schedule [_watcher.events.listen] so that the watcher doesn't start
-  // watching [dir] before it exists. Expose [_watcherEvents] immediately so
+  // watching [path] before it exists. Expose [_watcherEvents] immediately so
   // that it can be accessed synchronously after this.
   _watcherEvents = new ScheduledStream(futureStream(schedule(() {
     currentSchedule.onComplete.schedule(() {
@@ -139,8 +139,7 @@
 /// Whether an event to close [_watcherEvents] has been scheduled.
 bool _closePending = false;
 
-/// Schedule closing the directory watcher stream after the event queue has been
-/// pumped.
+/// Schedule closing the watcher stream after the event queue has been pumped.
 ///
 /// This is necessary when events are allowed to occur, but don't have to occur,
 /// at the end of a test. Otherwise, if they don't occur, the test will wait