Folders containing overlayed files should exist even if they do not exist on disk

Change-Id: I511b5075cb10b2594dce9dc33f165b64d9527043
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103565
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Auto-Submit: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/integration/analysis/error_test.dart b/pkg/analysis_server/test/integration/analysis/error_test.dart
index 0a7948b..7d5cd76 100644
--- a/pkg/analysis_server/test/integration/analysis/error_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/error_test.dart
@@ -18,6 +18,24 @@
 @reflectiveTest
 class AnalysisErrorIntegrationTest
     extends AbstractAnalysisServerIntegrationTest {
+  test_analysisRootDoesNotExist() async {
+    String packagePath = sourcePath('package');
+    String filePath = sourcePath('package/lib/test.dart');
+    String content = '''
+main() {
+  print(null) // parse error: missing ';'
+}''';
+    await sendServerSetSubscriptions([ServerService.STATUS]);
+    await sendAnalysisUpdateContent({filePath: AddContentOverlay(content)});
+    await sendAnalysisSetAnalysisRoots([packagePath], []);
+    await analysisFinished;
+
+    expect(currentAnalysisErrors[filePath], isList);
+    List<AnalysisError> errors = currentAnalysisErrors[filePath];
+    expect(errors, hasLength(1));
+    expect(errors[0].location.file, equals(filePath));
+  }
+
   test_detect_simple_error() {
     String pathname = sourcePath('test.dart');
     writeFile(pathname, '''
diff --git a/pkg/analyzer/lib/file_system/overlay_file_system.dart b/pkg/analyzer/lib/file_system/overlay_file_system.dart
index 76879be..2f15a6a 100644
--- a/pkg/analyzer/lib/file_system/overlay_file_system.dart
+++ b/pkg/analyzer/lib/file_system/overlay_file_system.dart
@@ -70,6 +70,11 @@
 
   @override
   Resource getResource(String path) {
+    if (hasOverlay(path)) {
+      return new _OverlayResource._from(this, baseProvider.getFile(path));
+    } else if (_hasOverlayIn(path)) {
+      return new _OverlayResource._from(this, baseProvider.getFolder(path));
+    }
     return new _OverlayResource._from(this, baseProvider.getResource(path));
   }
 
@@ -140,14 +145,18 @@
   }
 
   /**
-   * Return the paths of all of the overlaid files that are immediate children
-   * of the given [folder].
+   * Return `true` if there is an overlay associated with at least one file
+   * contained inside the folder with the given [folderPath].
    */
-  Iterable<String> _overlaysInFolder(Folder folder) {
-    String folderPath = folder.path;
-    return _overlayContent.keys
-        .where((path) => pathContext.dirname(path) == folderPath);
-  }
+  bool _hasOverlayIn(String folderPath) => _overlayContent.keys
+      .any((filePath) => pathContext.isWithin(folderPath, filePath));
+
+  /**
+   * Return the paths of all of the overlaid files that are children of the
+   * given [folder], either directly or indirectly.
+   */
+  Iterable<String> _overlaysInFolder(String folderPath) => _overlayContent.keys
+      .where((filePath) => pathContext.isWithin(folderPath, filePath));
 }
 
 /**
@@ -281,7 +290,7 @@
   Stream<WatchEvent> get changes => _folder.changes;
 
   @override
-  bool get exists => _resource.exists;
+  bool get exists => _provider._hasOverlayIn(path) || _resource.exists;
 
   /**
    * Return the folder from the base resource provider that corresponds to this
@@ -330,12 +339,24 @@
 
   @override
   List<Resource> getChildren() {
-    List<Resource> children = _folder
-        .getChildren()
-        .map((child) => new _OverlayResource._from(_provider, child))
-        .toList();
-    for (String overlayPath in _provider._overlaysInFolder(this)) {
-      children.add(_provider.getFile(overlayPath));
+    List<Resource> children;
+    try {
+      children = _folder
+          .getChildren()
+          .map((child) => new _OverlayResource._from(_provider, child))
+          .toList();
+    } on FileSystemException {
+      children = [];
+    }
+    for (String overlayPath in _provider._overlaysInFolder(path)) {
+      pathos.Context context = _provider.pathContext;
+      if (context.dirname(overlayPath) == path) {
+        children.add(_provider.getFile(overlayPath));
+      } else {
+        String relativePath = context.relative(overlayPath, from: path);
+        String folderName = context.split(relativePath)[0];
+        children.add(_provider.getFolder(context.join(path, folderName)));
+      }
     }
     return children;
   }
diff --git a/pkg/analyzer/test/file_system/overlay_file_system_test.dart b/pkg/analyzer/test/file_system/overlay_file_system_test.dart
index c422ade..f852767 100644
--- a/pkg/analyzer/test/file_system/overlay_file_system_test.dart
+++ b/pkg/analyzer/test/file_system/overlay_file_system_test.dart
@@ -506,7 +506,7 @@
     expect(child, isNotNull);
   }
 
-  test_getChildren() {
+  test_getChildren_existing() {
     Folder folder = _folder(exists: true);
     Folder child1 = _folder(
         exists: true, path: provider.pathContext.join(folder.path, 'lib'));
@@ -523,6 +523,13 @@
         unorderedEquals([child1.path, child2.path, child3.path]));
   }
 
+  test_getChildren_nonExisting_withOverlay() {
+    File file = _file(exists: false, withOverlay: true);
+    List<Resource> children = file.parent.parent.getChildren();
+    expect(children, hasLength(1));
+    expect(children[0], _isFolder);
+  }
+
   test_isOrContains_false() {
     Folder folder = _folder(exists: true);
     expect(folder.isOrContains(baseProvider.convertPath('/foo/baz')), isFalse);
@@ -662,20 +669,28 @@
     expect(file.exists, isTrue);
   }
 
-  test_getFolder_existing() {
+  test_getFolder_existing_withoutOverlay() {
     Folder folder = _folder(exists: true);
     expect(folder, isNotNull);
     expect(folder.path, defaultFolderPath);
     expect(folder.exists, isTrue);
   }
 
-  test_getFolder_notExisting() {
+  test_getFolder_notExisting_withoutOverlay() {
     Folder folder = _folder(exists: false);
     expect(folder, isNotNull);
     expect(folder.path, defaultFolderPath);
     expect(folder.exists, isFalse);
   }
 
+  test_getFolder_notExisting_withOverlay() {
+    File file = _file(exists: false, withOverlay: true);
+    Folder folder = file.parent;
+    expect(folder, isNotNull);
+    expect(folder.path, defaultFolderPath);
+    expect(folder.exists, isTrue);
+  }
+
   test_getModificationTimes_withoutOverlay() async {
     Source source = _file(exists: true).createSource();
     List<int> times = await provider.getModificationTimes([source]);
@@ -688,36 +703,43 @@
     expect(times, [42]);
   }
 
-  test_getResource_existingFile_withoutOverlay() {
+  test_getResource_file_existing_withoutOverlay() {
     String path = _file(exists: true).path;
     Resource resource = provider.getResource(path);
     expect(resource, _isFile);
   }
 
-  test_getResource_existingFile_withOverlay() {
+  test_getResource_file_existing_withOverlay() {
     String path = _file(exists: true, withOverlay: true).path;
     Resource resource = provider.getResource(path);
     expect(resource, _isFile);
   }
 
-  test_getResource_existingFolder() {
-    String path = _folder(exists: true).path;
-    Resource resource = provider.getResource(path);
-    expect(resource, _isFolder);
-  }
-
-  test_getResource_notExisting_withoutOverlay() {
+  test_getResource_file_notExisting_withoutOverlay() {
     String path = _file(exists: false).path;
     Resource resource = provider.getResource(path);
     expect(resource, _isFile);
   }
 
-  test_getResource_notExisting_withOverlay() {
+  test_getResource_file_notExisting_withOverlay() {
     String path = _file(exists: false, withOverlay: true).path;
     Resource resource = provider.getResource(path);
     expect(resource, _isFile);
   }
 
+  test_getResource_folder_existing() {
+    String path = _folder(exists: true).path;
+    Resource resource = provider.getResource(path);
+    expect(resource, _isFolder);
+  }
+
+  test_getResource_folder_nonExisting_withOverlay() {
+    String filePath = _file(exists: false, withOverlay: true).path;
+    String folderPath = provider.pathContext.dirname(filePath);
+    Resource resource = provider.getResource(folderPath);
+    expect(resource, _isFolder);
+  }
+
   test_getStateLocation_uniqueness() {
     String idOne = 'one';
     Folder folderOne = provider.getStateLocation(idOne);