Search all directories when attempting to analyze manifest files

Change-Id: I3a900362d8d36d27e5c22f600db7bc54059c7717
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/99401
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index b8adeca..c4dc5b8 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -23,8 +23,8 @@
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/source_io.dart';
-import 'package:analyzer/src/plugin/resolver_provider.dart';
 import 'package:analyzer/src/manifest/manifest_validator.dart';
+import 'package:analyzer/src/plugin/resolver_provider.dart';
 import 'package:analyzer/src/pubspec/pubspec_validator.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/source/path_filter.dart';
@@ -870,6 +870,32 @@
   }
 
   /**
+   * Use the given analysis [driver] to analyze the content of the 
+   * AndroidManifest file at the given [path].
+   */
+  void _analyzeManifestFile(AnalysisDriver driver, String path) {
+    List<protocol.AnalysisError> convertedErrors;
+    try {
+      String content = _readFile(path);
+      ManifestValidator validator =
+          new ManifestValidator(resourceProvider.getFile(path).createSource());
+      LineInfo lineInfo = _computeLineInfo(content);
+      List<AnalysisError> errors = validator.validate(
+          content, driver.analysisOptions.chromeOsManifestChecks);
+      AnalyzerConverter converter = new AnalyzerConverter();
+      convertedErrors = converter.convertAnalysisErrors(errors,
+          lineInfo: lineInfo, options: driver.analysisOptions);
+    } catch (exception) {
+      // If the file cannot be analyzed, fall through to clear any previous
+      // errors.
+    }
+    callbacks.notificationManager.recordAnalysisErrors(
+        NotificationManager.serverId,
+        path,
+        convertedErrors ?? <protocol.AnalysisError>[]);
+  }
+
+  /**
    * Use the given analysis [driver] to analyze the content of the pubspec file
    * at the given [path].
    */
@@ -897,32 +923,6 @@
         convertedErrors ?? <protocol.AnalysisError>[]);
   }
 
-  /**
-   * Use the given analysis [driver] to analyze the content of the 
-   * AndroidManifest file at the given [path].
-   */
-  void _analyzeManifestFile(AnalysisDriver driver, String path) {
-    List<protocol.AnalysisError> convertedErrors;
-    try {
-      String content = _readFile(path);
-      ManifestValidator validator =
-          new ManifestValidator(resourceProvider.getFile(path).createSource());
-      LineInfo lineInfo = _computeLineInfo(content);
-      List<AnalysisError> errors = validator.validate(
-          content, driver.analysisOptions.chromeOsManifestChecks);
-      AnalyzerConverter converter = new AnalyzerConverter();
-      convertedErrors = converter.convertAnalysisErrors(errors,
-          lineInfo: lineInfo, options: driver.analysisOptions);
-    } catch (exception) {
-      // If the file cannot be analyzed, fall through to clear any previous
-      // errors.
-    }
-    callbacks.notificationManager.recordAnalysisErrors(
-        NotificationManager.serverId,
-        path,
-        convertedErrors ?? <protocol.AnalysisError>[]);
-  }
-
   void _checkForAnalysisOptionsUpdate(String path, ContextInfo info) {
     if (AnalysisEngine.isAnalysisOptionsFileName(path, pathContext)) {
       AnalysisDriver driver = info.analysisDriver;
@@ -938,6 +938,19 @@
     }
   }
 
+  void _checkForManifestUpdate(String path, ContextInfo info) {
+    if (_isManifest(path)) {
+      AnalysisDriver driver = info.analysisDriver;
+      if (driver == null) {
+        // I suspect that this happens as a result of a race condition: server
+        // has determined that the file (at [path]) is in a context, but hasn't
+        // yet created a driver for that context.
+        return;
+      }
+      _analyzeManifestFile(driver, path);
+    }
+  }
+
   void _checkForPackagespecUpdate(String path, ContextInfo info) {
     // Check to see if this is the .packages file for this context and if so,
     // update the context's source factory.
@@ -968,19 +981,6 @@
     }
   }
 
-  void _checkForManifestUpdate(String path, ContextInfo info) {
-    if (_isManifest(path)) {
-      AnalysisDriver driver = info.analysisDriver;
-      if (driver == null) {
-        // I suspect that this happens as a result of a race condition: server
-        // has determined that the file (at [path]) is in a context, but hasn't
-        // yet created a driver for that context.
-        return;
-      }
-      _analyzeManifestFile(driver, path);
-    }
-  }
-
   /**
    * Compute the set of files that are being flushed, this is defined as
    * the set of sources in the removed context (context.sources), that are
@@ -1139,10 +1139,23 @@
     if (pubspecFile.exists) {
       _analyzePubspecFile(info.analysisDriver, pubspecFile.path);
     }
-    File manifestFile = folder.getChildAssumingFile(MANIFEST_NAME);
-    if (manifestFile.exists) {
-      _analyzeManifestFile(info.analysisDriver, manifestFile.path);
+
+    void checkManifestFilesIn(Folder folder) {
+      for (var child in folder.getChildren()) {
+        if (child is File) {
+          if (child.shortName == MANIFEST_NAME &&
+              !excludedPaths.contains(child.path)) {
+            _analyzeManifestFile(info.analysisDriver, child.path);
+          }
+        } else if (child is Folder) {
+          if (!excludedPaths.contains(child.path)) {
+            checkManifestFilesIn(child);
+          }
+        }
+      }
     }
+
+    checkManifestFilesIn(folder);
     return info;
   }
 
@@ -1510,13 +1523,13 @@
     return false;
   }
 
+  bool _isManifest(String path) => pathContext.basename(path) == MANIFEST_NAME;
+
   bool _isPackagespec(String path) =>
       pathContext.basename(path) == PACKAGE_SPEC_NAME;
 
   bool _isPubspec(String path) => pathContext.basename(path) == PUBSPEC_NAME;
 
-  bool _isManifest(String path) => pathContext.basename(path) == MANIFEST_NAME;
-
   /**
    * Merges [info] context into its parent.
    */
diff --git a/pkg/analysis_server/test/analysis/notification_errors_test.dart b/pkg/analysis_server/test/analysis/notification_errors_test.dart
index a270f6c..df6f051 100644
--- a/pkg/analysis_server/test/analysis/notification_errors_test.dart
+++ b/pkg/analysis_server/test/analysis/notification_errors_test.dart
@@ -66,6 +66,36 @@
     expect(error.type, AnalysisErrorType.STATIC_WARNING);
   }
 
+  test_androidManifestFile() async {
+    String filePath = join(projectPath, 'android', 'AndroidManifest.xml');
+    String manifestFile = newFile(filePath, content: '''
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-feature android:name="android.software.home_screen" />
+</manifest>
+''').path;
+    newFile(join(projectPath, 'analysis_options.yaml'), content: '''
+analyzer:
+  optional-checks:
+    chrome-os-manifest-checks: true
+''');
+
+    Request request =
+        new AnalysisSetAnalysisRootsParams([projectPath], []).toRequest('0');
+    handleSuccessfulRequest(request);
+    await waitForTasksFinished();
+    await pumpEventQueue();
+    //
+    // Verify the error result.
+    //
+    List<AnalysisError> errors = filesErrors[manifestFile];
+    expect(errors, hasLength(1));
+    AnalysisError error = errors[0];
+    expect(error.location.file, filePath);
+    expect(error.severity, AnalysisErrorSeverity.WARNING);
+    expect(error.type, AnalysisErrorType.STATIC_WARNING);
+  }
+
   test_importError() async {
     createProject();