Make ContextRoot part of the API to support the angular plugin, fix some bugs

Change-Id: I3f23d1712473c4e5522c441ac31e013516ae5f4d
Reviewed-on: https://dart-review.googlesource.com/43880
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/dart/analysis/context_locator.dart b/pkg/analyzer/lib/dart/analysis/context_locator.dart
index 9c7f979..ce4d07a 100644
--- a/pkg/analyzer/lib/dart/analysis/context_locator.dart
+++ b/pkg/analyzer/lib/dart/analysis/context_locator.dart
@@ -1,8 +1,9 @@
-// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/context_locator.dart';
 import 'package:meta/meta.dart';
@@ -11,6 +12,8 @@
  * Determines the list of analysis contexts that can be used to analyze the
  * files and folders that should be analyzed given a list of included files and
  * folders and a list of excluded files and folders.
+ *
+ * Clients may not extend, implement or mix-in this class.
  */
 abstract class ContextLocator {
   /**
@@ -26,16 +29,43 @@
    * files that are included by the list of [includedPaths] and not excluded by
    * the list of [excludedPaths].
    *
-   * If the [packagesFile] is specified, then it is assumed to be the path to
-   * the `.packages` file that should be used in place of the one that would be
+   * If an [optionsFile] is specified, then it is assumed to be the path to the
+   * `analysis_options.yaml` (or `.analysis_options`) file that should be used
+   * in place of the ones that would be found by looking in the directories
+   * containing the context roots.
+   *
+   * If a [packagesFile] is specified, then it is assumed to be the path to the
+   * `.packages` file that should be used in place of the one that would be
    * found by looking in the directories containing the context roots.
    *
    * If the [sdkPath] is specified, then it is used as the path to the root of
    * the SDK that should be used during analysis.
    */
+  @deprecated
   List<AnalysisContext> locateContexts(
       {@required List<String> includedPaths,
       List<String> excludedPaths: const <String>[],
+      String optionsFile: null,
       String packagesFile: null,
       String sdkPath: null});
+
+  /**
+   * Return a list of the context roots that should be used to analyze the files
+   * that are included by the list of [includedPaths] and not excluded by the
+   * list of [excludedPaths].
+   *
+   * If an [optionsFile] is specified, then it is assumed to be the path to the
+   * `analysis_options.yaml` (or `.analysis_options`) file that should be used
+   * in place of the ones that would be found by looking in the directories
+   * containing the context roots.
+   *
+   * If a [packagesFile] is specified, then it is assumed to be the path to the
+   * `.packages` file that should be used in place of the one that would be
+   * found by looking in the directories containing the context roots.
+   */
+  List<ContextRoot> locateRoots(
+      {@required List<String> includedPaths,
+      List<String> excludedPaths: null,
+      String optionsFile: null,
+      String packagesFile: null});
 }
diff --git a/pkg/analyzer/lib/dart/analysis/context_root.dart b/pkg/analyzer/lib/dart/analysis/context_root.dart
new file mode 100644
index 0000000..f710dbd
--- /dev/null
+++ b/pkg/analyzer/lib/dart/analysis/context_root.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/file_system/file_system.dart';
+
+/**
+ * Information about the root directory associated with an analysis context.
+ *
+ * Clients may not extend, implement or mix-in this class.
+ */
+abstract class ContextRoot {
+  /**
+   * A list of the files and directories within the root directory that should
+   * not be analyzed.
+   */
+  List<Resource> get excluded;
+
+  /**
+   * A collection of the absolute, normalized paths of files and directories
+   * within the root directory that should not be analyzed.
+   */
+  Iterable<String> get excludedPaths;
+
+  /**
+   * A list of the files and directories within the root directory that should
+   * be analyzed. If all of the files in the root directory (other than those
+   * that are explicitly excluded) should be analyzed, then this list will
+   * contain the root directory.
+   */
+  List<Resource> get included;
+
+  /**
+   * A collection of the absolute, normalized paths of files within the root
+   * directory that should be analyzed. If all of the files in the root
+   * directory (other than those that are explicitly excluded) should be
+   * analyzed, then this collection will contain the path of the root directory.
+   */
+  Iterable<String> get includedPaths;
+
+  /**
+   * The analysis options file that should be used when analyzing the files
+   * within this context root, or `null` if there is no options file.
+   */
+  File get optionsFile;
+
+  /**
+   * The packages file that should be used when analyzing the files within this
+   * context root, or `null` if there is no options file.
+   */
+  File get packagesFile;
+
+  /**
+   * The root directory containing the files to be analyzed.
+   */
+  Folder get root;
+}
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
index 0a7ca25..e9fbaa9 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -7,11 +7,13 @@
 import 'package:analyzer/context/context_root.dart' as old;
 import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/physical_file_system.dart'
     show PhysicalResourceProvider;
 import 'package:analyzer/src/context/builder.dart'
     show ContextBuilder, ContextBuilderOptions;
+import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart'
     show AnalysisDriver, AnalysisDriverScheduler;
 import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
@@ -68,18 +70,22 @@
   String get _defaultSdkPath =>
       FolderBasedDartSdk.defaultSdkDirectory(resourceProvider).path;
 
+  @deprecated
   @override
   List<AnalysisContext> locateContexts(
       {@required List<String> includedPaths,
       List<String> excludedPaths: null,
+      String optionsFile: null,
       String packagesFile: null,
       String sdkPath: null}) {
-    if (includedPaths == null || includedPaths.isEmpty) {
-      throw new ArgumentError('There must be at least one included path');
+    List<ContextRoot> roots = locateRoots(
+        includedPaths: includedPaths,
+        excludedPaths: excludedPaths,
+        optionsFile: optionsFile,
+        packagesFile: packagesFile);
+    if (roots.isEmpty) {
+      return const <AnalysisContext>[];
     }
-    List<AnalysisContext> contextList = <AnalysisContext>[];
-    List<ContextRoot> roots =
-        locateRoots(includedPaths, excludedPaths: excludedPaths);
     PerformanceLog performanceLog = new PerformanceLog(new StringBuffer());
     AnalysisDriverScheduler scheduler =
         new AnalysisDriverScheduler(performanceLog);
@@ -97,33 +103,33 @@
     builder.byteStore = new MemoryByteStore();
     builder.fileContentOverlay = new FileContentOverlay();
     builder.performanceLog = performanceLog;
+    List<AnalysisContext> contextList = <AnalysisContext>[];
     for (ContextRoot root in roots) {
       old.ContextRoot contextRoot =
-          new old.ContextRoot(root.root.path, root.excludedPaths);
+          new old.ContextRoot(root.root.path, root.excludedPaths.toList());
       AnalysisDriver driver = builder.buildDriver(contextRoot);
       DriverBasedAnalysisContext context =
           new DriverBasedAnalysisContext(resourceProvider, driver);
-      context.includedPaths = root.includedPaths;
-      context.excludedPaths = root.excludedPaths;
+      context.includedPaths = root.includedPaths.toList();
+      context.excludedPaths = root.excludedPaths.toList();
       contextList.add(context);
     }
     return contextList;
   }
 
-  /**
-   * Return a list of the context roots that should be used to analyze the files
-   * that are included by the list of [includedPaths] and not excluded by the
-   * list of [excludedPaths].
-   */
-  @visibleForTesting
-  List<ContextRoot> locateRoots(List<String> includedPaths,
-      {List<String> excludedPaths}) {
+  @override
+  List<ContextRoot> locateRoots(
+      {@required List<String> includedPaths,
+      List<String> excludedPaths: null,
+      String optionsFile: null,
+      String packagesFile: null}) {
     //
     // Compute the list of folders and files that are to be included.
     //
     List<Folder> includedFolders = <Folder>[];
     List<File> includedFiles = <File>[];
-    _resourcesFromPaths(includedPaths, includedFolders, includedFiles);
+    _resourcesFromPaths(
+        includedPaths ?? const <String>[], includedFolders, includedFiles);
     //
     // Compute the list of folders and files that are to be excluded.
     //
@@ -151,19 +157,42 @@
     // analyzed. For each, walk the directory structure and figure out where to
     // create context roots.
     //
+    File defaultOptionsFile;
+    if (optionsFile != null) {
+      defaultOptionsFile = resourceProvider.getFile(optionsFile);
+      if (!defaultOptionsFile.exists) {
+        defaultOptionsFile = null;
+      }
+    }
+    File defaultPackagesFile;
+    if (packagesFile != null) {
+      defaultPackagesFile = resourceProvider.getFile(packagesFile);
+      if (!defaultPackagesFile.exists) {
+        defaultPackagesFile = null;
+      }
+    }
     List<ContextRoot> roots = <ContextRoot>[];
     for (Folder folder in includedFolders) {
-      _createContextRoots(roots, folder, excludedFolders, null);
+      ContextRootImpl root = new ContextRootImpl(folder);
+      root.packagesFile = defaultPackagesFile ?? _findPackagesFile(folder);
+      root.optionsFile = defaultOptionsFile ?? _findOptionsFile(folder);
+      root.included.add(folder);
+      roots.add(root);
+      _createContextRootsIn(roots, folder, excludedFolders, root,
+          defaultOptionsFile, defaultPackagesFile);
     }
+    Map<Folder, ContextRoot> rootMap = <Folder, ContextRoot>{};
     for (File file in includedFiles) {
       Folder parent = file.parent;
-      ContextRoot root = new ContextRoot(file);
-      root.packagesFile = _findPackagesFile(parent);
-      root.optionsFile = _findOptionsFile(parent);
+      ContextRoot root = rootMap.putIfAbsent(parent, () {
+        ContextRootImpl root = new ContextRootImpl(parent);
+        root.packagesFile = defaultPackagesFile ?? _findPackagesFile(parent);
+        root.optionsFile = defaultOptionsFile ?? _findOptionsFile(parent);
+        roots.add(root);
+        return root;
+      });
       root.included.add(file);
-      roots.add(root);
     }
-
     return roots;
   }
 
@@ -174,31 +203,74 @@
   bool _containedInAny(Iterable<Folder> folders, Resource resource) =>
       folders.any((Folder folder) => folder.contains(resource.path));
 
-  void _createContextRoots(List<ContextRoot> roots, Folder folder,
-      List<Folder> excludedFolders, ContextRoot containingRoot) {
+  /**
+   * If the given [folder] should be the root of a new analysis context, then
+   * create a new context root for it and add it to the list of context [roots].
+   * The [containingRoot] is the context root from an enclosing directory and is
+   * used to inherit configuration information that isn't overridden.
+   *
+   * If either the [optionsFile] or [packagesFile] is non-`null` then the given
+   * file will be used even if there is a local version of the file.
+   *
+   * For each directory within the given [folder] that is not in the list of
+   * [excludedFolders], recursively search for nested context roots.
+   */
+  void _createContextRoots(
+      List<ContextRoot> roots,
+      Folder folder,
+      List<Folder> excludedFolders,
+      ContextRoot containingRoot,
+      File optionsFile,
+      File packagesFile) {
     //
-    // Create a context root for the given [folder] is appropriate.
+    // If the options and packages files are allowed to be locally specified,
+    // then look to see whether they are.
     //
-    if (containingRoot == null) {
-      ContextRoot root = new ContextRoot(folder);
-      root.packagesFile = _findPackagesFile(folder);
-      root.optionsFile = _findOptionsFile(folder);
+    File localOptionsFile;
+    if (optionsFile == null) {
+      localOptionsFile = _getOptionsFile(folder);
+    }
+    File localPackagesFile;
+    if (packagesFile == null) {
+      localPackagesFile = _getPackagesFile(folder);
+    }
+    //
+    // Create a context root for the given [folder] if at least one of the
+    // options and packages file is locally specified.
+    //
+    if (localPackagesFile != null || localOptionsFile != null) {
+      if (optionsFile != null) {
+        localOptionsFile = optionsFile;
+      }
+      if (packagesFile != null) {
+        localPackagesFile = packagesFile;
+      }
+      ContextRootImpl root = new ContextRootImpl(folder);
+      root.packagesFile = localPackagesFile ?? containingRoot.packagesFile;
+      root.optionsFile = localOptionsFile ?? containingRoot.optionsFile;
       root.included.add(folder);
+      containingRoot.excluded.add(folder);
       roots.add(root);
       containingRoot = root;
-    } else {
-      File packagesFile = _getPackagesFile(folder);
-      File optionsFile = _getOptionsFile(folder);
-      if (packagesFile != null || optionsFile != null) {
-        ContextRoot root = new ContextRoot(folder);
-        root.packagesFile = packagesFile ?? containingRoot.packagesFile;
-        root.optionsFile = optionsFile ?? containingRoot.optionsFile;
-        root.included.add(folder);
-        containingRoot.excluded.add(folder);
-        roots.add(root);
-        containingRoot = root;
-      }
     }
+    _createContextRootsIn(roots, folder, excludedFolders, containingRoot,
+        optionsFile, packagesFile);
+  }
+
+  /**
+   * For each directory within the given [folder] that is not in the list of
+   * [excludedFolders], recursively search for nested context roots.
+   *
+   * If either the [optionsFile] or [packagesFile] is non-`null` then the given
+   * file will be used even if there is a local version of the file.
+   */
+  void _createContextRootsIn(
+      List<ContextRoot> roots,
+      Folder folder,
+      List<Folder> excludedFolders,
+      ContextRoot containingRoot,
+      File optionsFile,
+      File packagesFile) {
     //
     // Check each of the subdirectories to see whether a context root needs to
     // be added for it.
@@ -211,7 +283,8 @@
               folder.shortName == PACKAGES_DIR_NAME) {
             containingRoot.excluded.add(folder);
           } else {
-            _createContextRoots(roots, child, excludedFolders, containingRoot);
+            _createContextRoots(roots, child, excludedFolders, containingRoot,
+                optionsFile, packagesFile);
           }
         }
       }
@@ -311,28 +384,3 @@
     return sortedPaths;
   }
 }
-
-@visibleForTesting
-class ContextRoot {
-  final Resource root;
-  final List<Resource> included = <Resource>[];
-  final List<Resource> excluded = <Resource>[];
-  File packagesFile;
-  File optionsFile;
-
-  ContextRoot(this.root);
-
-  List<String> get excludedPaths =>
-      excluded.map((Resource folder) => folder.path).toList();
-
-  @override
-  int get hashCode => root.path.hashCode;
-
-  List<String> get includedPaths =>
-      included.map((Resource folder) => folder.path).toList();
-
-  @override
-  bool operator ==(Object other) {
-    return other is ContextRoot && root.path == other.root.path;
-  }
-}
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_root.dart b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
new file mode 100644
index 0000000..607c9d2
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/analysis/context_root.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/context_root.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/**
+ * An implementation of a context root.
+ */
+class ContextRootImpl implements ContextRoot {
+  @override
+  final Folder root;
+
+  @override
+  final List<Resource> included = <Resource>[];
+
+  @override
+  final List<Resource> excluded = <Resource>[];
+
+  @override
+  File optionsFile;
+
+  @override
+  File packagesFile;
+
+  /**
+   * Initialize a newly created context root.
+   */
+  ContextRootImpl(this.root);
+
+  @override
+  Iterable<String> get excludedPaths =>
+      excluded.map((Resource folder) => folder.path);
+
+  @override
+  int get hashCode => root.path.hashCode;
+
+  @override
+  Iterable<String> get includedPaths =>
+      included.map((Resource folder) => folder.path);
+
+  @override
+  bool operator ==(Object other) {
+    return other is ContextRoot && root.path == other.root.path;
+  }
+}
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
index 03356db..558b2a1 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver_based_analysis_context.dart
@@ -44,14 +44,16 @@
   @override
   Iterable<String> analyzedFiles() sync* {
     for (String path in includedPaths) {
-      Resource resource = resourceProvider.getResource(path);
-      if (resource is File) {
-        yield path;
-      } else if (resource is Folder) {
-        yield* _includedFilesInFolder(resource);
-      } else {
-        Type type = resource.runtimeType;
-        throw new StateError('Unknown resource at path "$path" ($type)');
+      if (!_isExcluded(path)) {
+        Resource resource = resourceProvider.getResource(path);
+        if (resource is File) {
+          yield path;
+        } else if (resource is Folder) {
+          yield* _includedFilesInFolder(resource);
+        } else {
+          Type type = resource.runtimeType;
+          throw new StateError('Unknown resource at path "$path" ($type)');
+        }
       }
     }
   }
@@ -67,14 +69,16 @@
    */
   Iterable<String> _includedFilesInFolder(Folder folder) sync* {
     for (Resource resource in folder.getChildren()) {
-      if (resource is File) {
-        yield resource.path;
-      } else if (resource is Folder) {
-        yield* _includedFilesInFolder(resource);
-      } else {
-        String path = resource.path;
-        Type type = resource.runtimeType;
-        throw new StateError('Unknown resource at path "$path" ($type)');
+      String path = resource.path;
+      if (!_isExcluded(path)) {
+        if (resource is File) {
+          yield path;
+        } else if (resource is Folder) {
+          yield* _includedFilesInFolder(resource);
+        } else {
+          Type type = resource.runtimeType;
+          throw new StateError('Unknown resource at path "$path" ($type)');
+        }
       }
     }
   }
@@ -85,6 +89,11 @@
    */
   bool _isExcluded(String path) {
     Context context = resourceProvider.pathContext;
+    String name = context.basename(path);
+    if (name.startsWith('.') ||
+        (name == 'packages' && resourceProvider.getResource(path) is Folder)) {
+      return true;
+    }
     for (String excludedPath in excludedPaths) {
       if (context.isAbsolute(excludedPath)) {
         if (context.isWithin(excludedPath, path)) {
diff --git a/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart b/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
index 029ad6b..b3f133b 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_locator_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/context_locator.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
@@ -56,8 +57,8 @@
     File outerPackagesFile = newPackagesFile('/test/outer');
     Folder innerRootFolder = newFolder('/test/outer/examples/inner');
 
-    List<ContextRoot> roots = contextLocator
-        .locateRoots([outerRootFolder.path, innerRootFolder.path]);
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path, innerRootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -73,8 +74,8 @@
     File outerPackagesFile = newPackagesFile('/test/outer');
     File testFile = newFile('/test/outer/examples/inner/test.dart');
 
-    List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path, testFile.path]);
+    List<ContextRoot> roots = contextLocator
+        .locateRoots(includedPaths: [outerRootFolder.path, testFile.path]);
     expect(roots, hasLength(1));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -93,8 +94,8 @@
     File outer2OptionsFile = newOptionsFile('/test/outer2');
     File outer2PackagesFile = newPackagesFile('/test/outer2');
 
-    List<ContextRoot> roots = contextLocator
-        .locateRoots([outer1RootFolder.path, outer2RootFolder.path]);
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outer1RootFolder.path, outer2RootFolder.path]);
     expect(roots, hasLength(2));
 
     ContextRoot outer1Root = findRoot(roots, outer1RootFolder);
@@ -119,8 +120,8 @@
     File outer2PackagesFile = newPackagesFile('/test/outer2');
     File testFile = newFile('/test/outer2/test.dart');
 
-    List<ContextRoot> roots =
-        contextLocator.locateRoots([outer1RootFolder.path, testFile.path]);
+    List<ContextRoot> roots = contextLocator
+        .locateRoots(includedPaths: [outer1RootFolder.path, testFile.path]);
     expect(roots, hasLength(2));
 
     ContextRoot outer1Root = findRoot(roots, outer1RootFolder);
@@ -129,7 +130,7 @@
     expect(outer1Root.optionsFile, outer1OptionsFile);
     expect(outer1Root.packagesFile, outer1PackagesFile);
 
-    ContextRoot outer2Root = findRoot(roots, testFile);
+    ContextRoot outer2Root = findRoot(roots, testFile.parent);
     expect(outer2Root.includedPaths, unorderedEquals([testFile.path]));
     expect(outer2Root.excludedPaths, isEmpty);
     expect(outer2Root.optionsFile, outer2OptionsFile);
@@ -161,21 +162,16 @@
     File testFile1 = newFile('/test/root/test1.dart');
     File testFile2 = newFile('/test/root/test2.dart');
 
-    List<ContextRoot> roots =
-        contextLocator.locateRoots([testFile1.path, testFile2.path]);
-    expect(roots, hasLength(2));
+    List<ContextRoot> roots = contextLocator
+        .locateRoots(includedPaths: [testFile1.path, testFile2.path]);
+    expect(roots, hasLength(1));
 
-    ContextRoot outer1Root = findRootFromIncluded(roots, testFile1.path);
-    expect(outer1Root.includedPaths, unorderedEquals([testFile1.path]));
-    expect(outer1Root.excludedPaths, isEmpty);
-    expect(outer1Root.optionsFile, optionsFile);
-    expect(outer1Root.packagesFile, packagesFile);
-
-    ContextRoot outer2Root = findRootFromIncluded(roots, testFile2.path);
-    expect(outer2Root.includedPaths, unorderedEquals([testFile2.path]));
-    expect(outer2Root.excludedPaths, isEmpty);
-    expect(outer2Root.optionsFile, optionsFile);
-    expect(outer2Root.packagesFile, packagesFile);
+    ContextRoot root = findRootFromIncluded(roots, testFile1.path);
+    expect(
+        root.includedPaths, unorderedEquals([testFile1.path, testFile2.path]));
+    expect(root.excludedPaths, isEmpty);
+    expect(root.optionsFile, optionsFile);
+    expect(root.packagesFile, packagesFile);
   }
 
   void test_locateRoots_nested_excluded_dot() {
@@ -186,7 +182,7 @@
     newOptionsFile('/test/outer/.examples/inner');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -203,7 +199,8 @@
     Folder excludedFolder = newFolder('/test/outer/examples');
     newOptionsFile('/test/outer/examples/inner');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([outerRootFolder.path],
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
         excludedPaths: [excludedFolder.path]);
     expect(roots, hasLength(1));
 
@@ -222,7 +219,7 @@
     newOptionsFile('/test/outer/packages/inner');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -242,7 +239,7 @@
     File inner2PackagesFile = newPackagesFile('/test/outer/examples/inner2');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(3));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -273,7 +270,7 @@
     File innerOptionsFile = newOptionsFile('/test/outer/examples/inner');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(2));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -289,6 +286,52 @@
     expect(innerRoot.packagesFile, outerPackagesFile);
   }
 
+  void test_locateRoots_nested_options_overriddenOptions() {
+    Folder outerRootFolder = newFolder('/test/outer');
+    newOptionsFile('/test/outer');
+    File outerPackagesFile = newPackagesFile('/test/outer');
+    newFolder('/test/outer/examples/inner');
+    newOptionsFile('/test/outer/examples/inner');
+    File overrideOptionsFile = newOptionsFile('/test/override');
+
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
+        optionsFile: overrideOptionsFile.path);
+    expect(roots, hasLength(1));
+
+    ContextRoot outerRoot = findRoot(roots, outerRootFolder);
+    expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
+    expect(outerRoot.excludedPaths, isEmpty);
+    expect(outerRoot.optionsFile, overrideOptionsFile);
+    expect(outerRoot.packagesFile, outerPackagesFile);
+  }
+
+  void test_locateRoots_nested_options_overriddenPackages() {
+    Folder outerRootFolder = newFolder('/test/outer');
+    File outerOptionsFile = newOptionsFile('/test/outer');
+    newPackagesFile('/test/outer');
+    Folder innerRootFolder = newFolder('/test/outer/examples/inner');
+    File innerOptionsFile = newOptionsFile('/test/outer/examples/inner');
+    File overridePackagesFile = newPackagesFile('/test/override');
+
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
+        packagesFile: overridePackagesFile.path);
+    expect(roots, hasLength(2));
+
+    ContextRoot outerRoot = findRoot(roots, outerRootFolder);
+    expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
+    expect(outerRoot.excludedPaths, unorderedEquals([innerRootFolder.path]));
+    expect(outerRoot.optionsFile, outerOptionsFile);
+    expect(outerRoot.packagesFile, overridePackagesFile);
+
+    ContextRoot innerRoot = findRoot(roots, innerRootFolder);
+    expect(innerRoot.includedPaths, unorderedEquals([innerRootFolder.path]));
+    expect(innerRoot.excludedPaths, isEmpty);
+    expect(innerRoot.optionsFile, innerOptionsFile);
+    expect(innerRoot.packagesFile, overridePackagesFile);
+  }
+
   void test_locateRoots_nested_optionsAndPackages() {
     Folder outerRootFolder = newFolder('/test/outer');
     File outerOptionsFile = newOptionsFile('/test/outer');
@@ -298,7 +341,7 @@
     File innerPackagesFile = newPackagesFile('/test/outer/examples/inner');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(2));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -314,6 +357,29 @@
     expect(innerRoot.packagesFile, innerPackagesFile);
   }
 
+  void test_locateRoots_nested_optionsAndPackages_overriddenBoth() {
+    Folder outerRootFolder = newFolder('/test/outer');
+    newOptionsFile('/test/outer');
+    newPackagesFile('/test/outer');
+    newFolder('/test/outer/examples/inner');
+    newOptionsFile('/test/outer/examples/inner');
+    newPackagesFile('/test/outer/examples/inner');
+    File overrideOptionsFile = newOptionsFile('/test/override');
+    File overridePackagesFile = newPackagesFile('/test/override');
+
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
+        optionsFile: overrideOptionsFile.path,
+        packagesFile: overridePackagesFile.path);
+    expect(roots, hasLength(1));
+
+    ContextRoot outerRoot = findRoot(roots, outerRootFolder);
+    expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
+    expect(outerRoot.excludedPaths, isEmpty);
+    expect(outerRoot.optionsFile, overrideOptionsFile);
+    expect(outerRoot.packagesFile, overridePackagesFile);
+  }
+
   void test_locateRoots_nested_packages() {
     Folder outerRootFolder = newFolder('/test/outer');
     File outerOptionsFile = newOptionsFile('/test/outer');
@@ -322,7 +388,7 @@
     File innerPackagesFile = newPackagesFile('/test/outer/examples/inner');
 
     List<ContextRoot> roots =
-        contextLocator.locateRoots([outerRootFolder.path]);
+        contextLocator.locateRoots(includedPaths: [outerRootFolder.path]);
     expect(roots, hasLength(2));
 
     ContextRoot outerRoot = findRoot(roots, outerRootFolder);
@@ -338,12 +404,59 @@
     expect(innerRoot.packagesFile, innerPackagesFile);
   }
 
+  void test_locateRoots_nested_packages_overriddenOptions() {
+    Folder outerRootFolder = newFolder('/test/outer');
+    newOptionsFile('/test/outer');
+    File outerPackagesFile = newPackagesFile('/test/outer');
+    Folder innerRootFolder = newFolder('/test/outer/examples/inner');
+    File innerPackagesFile = newPackagesFile('/test/outer/examples/inner');
+    File overrideOptionsFile = newOptionsFile('/test/override');
+
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
+        optionsFile: overrideOptionsFile.path);
+    expect(roots, hasLength(2));
+
+    ContextRoot outerRoot = findRoot(roots, outerRootFolder);
+    expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
+    expect(outerRoot.excludedPaths, unorderedEquals([innerRootFolder.path]));
+    expect(outerRoot.optionsFile, overrideOptionsFile);
+    expect(outerRoot.packagesFile, outerPackagesFile);
+
+    ContextRoot innerRoot = findRoot(roots, innerRootFolder);
+    expect(innerRoot.includedPaths, unorderedEquals([innerRootFolder.path]));
+    expect(innerRoot.excludedPaths, isEmpty);
+    expect(innerRoot.optionsFile, overrideOptionsFile);
+    expect(innerRoot.packagesFile, innerPackagesFile);
+  }
+
+  void test_locateRoots_nested_packages_overriddenPackages() {
+    Folder outerRootFolder = newFolder('/test/outer');
+    File outerOptionsFile = newOptionsFile('/test/outer');
+    newPackagesFile('/test/outer');
+    newFolder('/test/outer/examples/inner');
+    newPackagesFile('/test/outer/examples/inner');
+    File overridePackagesFile = newPackagesFile('/test/override');
+
+    List<ContextRoot> roots = contextLocator.locateRoots(
+        includedPaths: [outerRootFolder.path],
+        packagesFile: overridePackagesFile.path);
+    expect(roots, hasLength(1));
+
+    ContextRoot outerRoot = findRoot(roots, outerRootFolder);
+    expect(outerRoot.includedPaths, unorderedEquals([outerRootFolder.path]));
+    expect(outerRoot.excludedPaths, isEmpty);
+    expect(outerRoot.optionsFile, outerOptionsFile);
+    expect(outerRoot.packagesFile, overridePackagesFile);
+  }
+
   void test_locateRoots_single_dir_directOptions_directPackages() {
     Folder rootFolder = newFolder('/test/root');
     File optionsFile = newOptionsFile('/test/root');
     File packagesFile = newPackagesFile('/test/root');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([rootFolder.path]);
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot package1Root = findRoot(roots, rootFolder);
@@ -358,7 +471,8 @@
     File optionsFile = newOptionsFile('/test/root');
     File packagesFile = newPackagesFile('/test');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([rootFolder.path]);
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot package1Root = findRoot(roots, rootFolder);
@@ -373,7 +487,8 @@
     File optionsFile = newOptionsFile('/test');
     File packagesFile = newPackagesFile('/test/root');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([rootFolder.path]);
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot package1Root = findRoot(roots, rootFolder);
@@ -388,7 +503,8 @@
     File optionsFile = newOptionsFile('/test');
     File packagesFile = newPackagesFile('/test');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([rootFolder.path]);
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [rootFolder.path]);
     expect(roots, hasLength(1));
 
     ContextRoot package1Root = findRoot(roots, rootFolder);
@@ -403,10 +519,11 @@
     File packagesFile = newPackagesFile('/test/root');
     File testFile = newFile('/test/root/test.dart');
 
-    List<ContextRoot> roots = contextLocator.locateRoots([testFile.path]);
+    List<ContextRoot> roots =
+        contextLocator.locateRoots(includedPaths: [testFile.path]);
     expect(roots, hasLength(1));
 
-    ContextRoot package1Root = findRoot(roots, testFile);
+    ContextRoot package1Root = findRoot(roots, testFile.parent);
     expect(package1Root.includedPaths, unorderedEquals([testFile.path]));
     expect(package1Root.excludedPaths, isEmpty);
     expect(package1Root.optionsFile, optionsFile);