Issue 61565. Use most nested package in PackageMapUriResolver. Use analysis_options.yaml file in intermadiate directories.

Bug: https://github.com/dart-lang/sdk/issues/61565
Change-Id: Ic2a37668b08dba481169d0d8ad97b23b52d8ae28
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464762
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
index 6252ec7..7cf64e3 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
@@ -406,28 +406,33 @@
     File? optionsFile,
     File? packagesFile,
   ) {
-    //
-    // If the options and packages files are allowed to be locally specified,
-    // then look to see whether they are.
-    //
-    File? localOptionsFile;
-    if (optionsFile == null) {
-      localOptionsFile = folder.existingAnalysisOptionsYamlFile;
-    }
-    File? localPackagesFile;
-    if (packagesFile == null) {
-      localPackagesFile = _getPackagesFile(folder);
-    }
+    var packagesFileToUse =
+        packagesFile ?? _getPackagesFile(folder) ?? containingRoot.packagesFile;
     var buildGnFile = folder.getExistingFile(file_paths.buildGn);
 
+    var optionsFileToUse = optionsFile;
+    if (optionsFileToUse == null) {
+      optionsFileToUse = folder.existingAnalysisOptionsYamlFile;
+      if (optionsFileToUse == null) {
+        var parentFolder = folder.parent;
+        while (parentFolder != containingRoot.root) {
+          optionsFileToUse = parentFolder.existingAnalysisOptionsYamlFile;
+          if (optionsFileToUse != null) {
+            break;
+          }
+          parentFolder = parentFolder.parent;
+        }
+      }
+    }
+
     var localEnabledPlugins = _getEnabledLegacyPlugins(
       containingRoot.workspace,
-      localOptionsFile,
+      optionsFileToUse,
     );
     // Legacy plugins differ only if there is an analysis_options and it
     // contains a different set of plugins from the containing context.
     var pluginsDiffer =
-        localOptionsFile != null &&
+        optionsFileToUse != null &&
         !const SetEquality<String>().equals(
           containingRootEnabledLegacyPlugins,
           localEnabledPlugins,
@@ -437,38 +442,20 @@
 
     // Create a context root for the given [folder] if a packages or build file
     // is locally specified, or the set of enabled legacy plugins changed.
-    if (pluginsDiffer || localPackagesFile != null || buildGnFile != null) {
-      if (optionsFile != null) {
-        localOptionsFile = optionsFile;
-      }
-      if (packagesFile != null) {
-        localPackagesFile = packagesFile;
-      }
-      var rootPackagesFile = localPackagesFile ?? containingRoot.packagesFile;
+    if (pluginsDiffer ||
+        packagesFileToUse != containingRoot.packagesFile ||
+        buildGnFile != null) {
       var workspace = _createWorkspace(
         folder: folder,
-        packagesFile: rootPackagesFile,
+        packagesFile: packagesFileToUse,
         buildGnFile: buildGnFile,
       );
-      // Check for analysis options file in the parent directories, from
-      // root folder to the containing root folder. Pick the one closest
-      // to the root.
-      if (localOptionsFile == null) {
-        var parentFolder = folder.parent;
-        while (parentFolder != containingRoot.root) {
-          localOptionsFile = parentFolder.existingAnalysisOptionsYamlFile;
-          if (localOptionsFile != null) {
-            break;
-          }
-          parentFolder = parentFolder.parent;
-        }
-      }
       var root = ContextRootImpl(
         resourceProvider,
         folder,
         workspace,
-        optionsFile: localOptionsFile ?? containingRoot.optionsFile,
-        packagesFile: rootPackagesFile,
+        optionsFile: optionsFileToUse ?? containingRoot.optionsFile,
+        packagesFile: packagesFileToUse,
       );
       root.included.add(folder);
       containingRoot.excluded.add(folder);
@@ -480,12 +467,12 @@
       usedThisRoot = false;
     }
 
-    if (localOptionsFile != null) {
+    if (optionsFileToUse != null) {
       (containingRoot as ContextRootImpl).optionsFileMap[folder] =
-          localOptionsFile;
+          optionsFileToUse;
       // Add excluded globs.
       var excludes = _getExcludedGlobs(
-        localOptionsFile,
+        optionsFileToUse,
         containingRoot.workspace,
       );
       containingRoot.excludedGlobs.addAll(excludes);
diff --git a/pkg/analyzer/lib/src/source/package_map_resolver.dart b/pkg/analyzer/lib/src/source/package_map_resolver.dart
index 92bd34f..5afd75b8 100644
--- a/pkg/analyzer/lib/src/source/package_map_resolver.dart
+++ b/pkg/analyzer/lib/src/source/package_map_resolver.dart
@@ -46,6 +46,8 @@
     // See for instance https://github.com/dart-lang/package_config/pull/117
     // for inspiration, but also, maybe just use package:package_config?
     pathos.Context pathContext = resourceProvider.pathContext;
+    Uri? bestUri;
+    int bestLength = -1;
     for (var packageEntry in packageMap.entries) {
       String pkgName = packageEntry.key;
       Folder pkgFolder = packageEntry.value[0];
@@ -53,13 +55,16 @@
       if (path.length >= pkgFolderPath.length + pathContext.separator.length &&
           path.startsWith(pkgFolderPath) &&
           path.startsWith(pathContext.separator, pkgFolderPath.length)) {
-        String relPath = path.substring(pkgFolderPath.length + 1);
-        List<String> relPathComponents = pathContext.split(relPath);
-        String relUriPath = pathos.posix.joinAll(relPathComponents);
-        return uriCache.parse('$_packageScheme:$pkgName/$relUriPath');
+        if (pkgFolderPath.length > bestLength) {
+          String relPath = path.substring(pkgFolderPath.length + 1);
+          List<String> relPathComponents = pathContext.split(relPath);
+          String relUriPath = pathos.posix.joinAll(relPathComponents);
+          bestUri = uriCache.parse('$_packageScheme:$pkgName/$relUriPath');
+          bestLength = pkgFolderPath.length;
+        }
       }
     }
-    return null;
+    return bestUri;
   }
 
   @override
diff --git a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart
index 7462647..a34178e 100644
--- a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart
@@ -335,7 +335,7 @@
         workspacePackage_0_0
       /home/test/lib/nestedNoYaml/a.dart
         uri: package:test/nestedNoYaml/a.dart
-        analysisOptions_0
+        analysisOptions_1
         workspacePackage_0_0
   /home/test/lib/nested
     packagesFile: /home/test/lib/nested/.dart_tool/package_config.json
@@ -344,11 +344,12 @@
     analyzedFiles
       /home/test/lib/nested/lib/c.dart
         uri: package:nested/c.dart
-        analysisOptions_1
+        analysisOptions_2
         workspacePackage_1_0
 analysisOptions
   analysisOptions_0: /home/test/lib/analysis_options.yaml
   analysisOptions_1: /home/test/lib/analysis_options.yaml
+  analysisOptions_2: /home/test/lib/analysis_options.yaml
 workspaces
   workspace_0: PackageConfigWorkspace
     root: /home/test
@@ -1547,6 +1548,126 @@
 ''');
   }
 
+  test_resolutionWorkspace_singlePackage_analysisOptions_intermediate() async {
+    var workspaceRootPath = '/home';
+    var package1RootPath = '$workspaceRootPath/packages/package1';
+
+    newPubspecYamlFile(workspaceRootPath, r'''
+name: root_package
+environment:
+  sdk: ^3.6.0
+workspace:
+  - packages/package1
+''');
+    newAnalysisOptionsYamlFile('$workspaceRootPath/packages', r'''
+linter:
+  rules:
+    - prefer_final_locals
+''');
+    newFile('$workspaceRootPath/lib/main.dart', '');
+
+    newPubspecYamlFile(package1RootPath, r'''
+name: package1
+environment:
+  sdk: ^3.6.0
+resolution: workspace
+''');
+    newFile('$package1RootPath/lib/package1.dart', '');
+
+    newPackageConfigJsonFileFromBuilder(
+      workspaceRootPath,
+      PackageConfigFileBuilder()
+        ..add(name: 'root_package', rootPath: workspaceRootPath)
+        ..add(name: 'package1', rootPath: package1RootPath),
+    );
+
+    var collection = AnalysisContextCollectionImpl(
+      resourceProvider: resourceProvider,
+      sdkPath: sdkRoot.path,
+      includedPaths: [getFolder(package1RootPath).path],
+      withFineDependencies: true,
+    );
+
+    configuration.withLintRules = true;
+    _assertCollectionText(collection, r'''
+contexts
+  /home
+    packagesFile: /home/.dart_tool/package_config.json
+    workspace: workspace_0
+    analyzedFiles
+      /home/packages/package1/lib/package1.dart
+        uri: package:package1/package1.dart
+        analysisOptions_0
+        workspacePackage_0_0
+analysisOptions
+  analysisOptions_0: /home/packages/analysis_options.yaml
+    lintRules
+      prefer_final_locals
+workspaces
+  workspace_0: PackageConfigWorkspace
+    root: /home
+    pubPackages
+      workspacePackage_0_0: PubPackage
+        root: /home/packages/package1
+        sdkVersionConstraint: ^3.6.0
+''');
+  }
+
+  test_resolutionWorkspace_singlePackage_nestedInLib() async {
+    var workspaceRootPath = '/home';
+    var package1RootPath = '$workspaceRootPath/lib/package1';
+
+    newPubspecYamlFile(workspaceRootPath, r'''
+name: root_package
+environment:
+  sdk: ^3.6.0
+workspace:
+  - lib/package1
+''');
+    newFile('$workspaceRootPath/lib/main.dart', '');
+
+    newPubspecYamlFile(package1RootPath, r'''
+name: package1
+environment:
+  sdk: ^3.6.0
+resolution: workspace
+''');
+    newFile('$package1RootPath/lib/package1.dart', '');
+
+    newPackageConfigJsonFileFromBuilder(
+      workspaceRootPath,
+      PackageConfigFileBuilder()
+        ..add(name: 'root_package', rootPath: workspaceRootPath)
+        ..add(name: 'package1', rootPath: package1RootPath),
+    );
+
+    var collection = AnalysisContextCollectionImpl(
+      resourceProvider: resourceProvider,
+      sdkPath: sdkRoot.path,
+      includedPaths: [getFolder(package1RootPath).path],
+      withFineDependencies: true,
+    );
+
+    // Note: `package:package1/package1.dart` URI.
+    _assertCollectionText(collection, r'''
+contexts
+  /home
+    packagesFile: /home/.dart_tool/package_config.json
+    workspace: workspace_0
+    analyzedFiles
+      /home/lib/package1/lib/package1.dart
+        uri: package:package1/package1.dart
+        workspacePackage_0_0
+workspaces
+  workspace_0: PackageConfigWorkspace
+    root: /home
+    pubPackages
+      workspacePackage_0_0: PubPackage
+        root: /home/lib/package1
+        sdkVersionConstraint: ^3.6.0
+''');
+  }
+
   void _assertCollectionText(
     AnalysisContextCollectionImpl collection,
     String expected,
diff --git a/pkg/analyzer/test/src/source/package_map_resolver_test.dart b/pkg/analyzer/test/src/source/package_map_resolver_test.dart
index 377fa47..966726a 100644
--- a/pkg/analyzer/test/src/source/package_map_resolver_test.dart
+++ b/pkg/analyzer/test/src/source/package_map_resolver_test.dart
@@ -71,6 +71,25 @@
     }
   }
 
+  void test_pathToUri_nestedInLib() {
+    String rootPkgLib = convertPath('/root/lib');
+    String nestedPkgLib = convertPath('/root/lib/nested/lib');
+    String nestedPkgFile = convertPath('/root/lib/nested/lib/nested.dart');
+
+    provider.newFile(nestedPkgFile, '');
+
+    PackageMapUriResolver resolver = PackageMapUriResolver(
+      provider,
+      <String, List<Folder>>{
+        'root': <Folder>[provider.getFolder(rootPkgLib)],
+        'nested': <Folder>[provider.getFolder(nestedPkgLib)],
+      },
+    );
+
+    var uri = resolver.pathToUri(nestedPkgFile);
+    expect(uri, Uri.parse('package:nested/nested.dart'));
+  }
+
   void test_resolve_multiple_folders() {
     var a = provider.newFile(convertPath('/aaa/a.dart'), '');
     var b = provider.newFile(convertPath('/bbb/b.dart'), '');