Version 2.14.0-57.0.dev

Merge commit '83a957f9bd596a933ba11d0257c068683eaa7db5' into 'dev'
diff --git a/pkg/analyzer/lib/src/source/path_filter.dart b/pkg/analyzer/lib/src/source/path_filter.dart
index 2851509..83a8983 100644
--- a/pkg/analyzer/lib/src/source/path_filter.dart
+++ b/pkg/analyzer/lib/src/source/path_filter.dart
@@ -5,29 +5,35 @@
 import 'package:analyzer/src/util/glob.dart';
 import 'package:path/path.dart' as path;
 
-/// Filter paths against a set of [_ignorePatterns] relative to a [root]
-/// directory. Paths outside of [root] are also ignored.
+/// Filter paths against a set of [_ignorePatterns] relative to a
+/// [ignorePatternsRoot] directory. Paths outside of [includedRoot] are also
+/// ignored.
 class PathFilter {
   /// The path context to use when manipulating paths.
   final path.Context pathContext;
 
+  /// The path in which files are considered to be included.
+  final String includedRoot;
+
   /// Path that all ignore patterns are relative to.
-  final String root;
+  final String ignorePatternsRoot;
 
   /// List of ignore patterns that paths are tested against.
   final List<Glob> _ignorePatterns = <Glob>[];
 
-  /// Construct a new path filter rooted at [root] with [ignorePatterns].
+  /// Construct a new path filter rooted at [includedRoot],
+  /// with [ignorePatterns] that are relative to [ignorePatternsRoot].
   /// If [pathContext] is not specified, then the system path context is used.
-  PathFilter(this.root, List<String> ignorePatterns,
+  PathFilter(
+      this.includedRoot, this.ignorePatternsRoot, List<String> ignorePatterns,
       [path.Context? pathContext])
       : pathContext = pathContext ?? path.context {
     setIgnorePatterns(ignorePatterns);
   }
 
   /// Returns true if [path] should be ignored. A path is ignored if it is not
-  /// contained in [root] or matches one of the ignore patterns.
-  /// [path] is absolute or relative to [root].
+  /// contained in [includedRoot] or matches one of the ignore patterns.
+  /// [path] is absolute or relative to [includedRoot].
   bool ignored(String path) {
     path = _canonicalize(path);
     return !_contained(path) || _match(path);
@@ -53,24 +59,21 @@
     return sb.toString();
   }
 
-  /// Returns the absolute path of [path], relative to [root].
+  /// Returns the absolute path of [path], relative to [includedRoot].
   String _canonicalize(String path) =>
-      pathContext.normalize(pathContext.join(root, path));
+      pathContext.normalize(pathContext.join(includedRoot, path));
 
-  /// Returns true when [path] is contained inside [root].
-  bool _contained(String path) => path.startsWith(root);
+  /// Returns true when [path] is contained inside [includedRoot].
+  bool _contained(String path) => path.startsWith(includedRoot);
 
   /// Returns true if [path] matches any ignore patterns.
   bool _match(String path) {
-    path = _relative(path);
+    var relative = pathContext.relative(path, from: ignorePatternsRoot);
     for (Glob glob in _ignorePatterns) {
-      if (glob.matches(path)) {
+      if (glob.matches(relative)) {
         return true;
       }
     }
     return false;
   }
-
-  /// Returns the relative portion of [path] from [root].
-  String _relative(String path) => pathContext.relative(path, from: root);
 }
diff --git a/pkg/analyzer/test/source/path_filter_test.dart b/pkg/analyzer/test/source/path_filter_test.dart
index 5a115f6..077a8c0 100644
--- a/pkg/analyzer/test/source/path_filter_test.dart
+++ b/pkg/analyzer/test/source/path_filter_test.dart
@@ -8,53 +8,68 @@
 
 main() {
   String root(String path) => context.absolute(context.normalize(path));
+
+  PathFilter withSingleRoot(String root, List<String> ignorePatterns) {
+    return PathFilter(root, root, ignorePatterns, context);
+  }
+
   group('PathFilterTest', () {
-    setUp(() {});
-    tearDown(() {});
     test('test_ignoreEverything', () {
-      var filter = PathFilter(root('/'), ['*'], context);
+      var filter = withSingleRoot(root('/'), ['*']);
       expect(filter.ignored('a'), isTrue);
     });
+
     test('test_ignoreFile', () {
-      var filter = PathFilter(root('/'), ['apple'], context);
+      var filter = withSingleRoot(root('/'), ['apple']);
       expect(filter.ignored('apple'), isTrue);
       expect(filter.ignored('banana'), isFalse);
     });
+
     test('test_ignoreMultipleFiles', () {
-      var filter = PathFilter(root('/'), ['apple', 'banana'], context);
+      var filter = withSingleRoot(root('/'), ['apple', 'banana']);
       expect(filter.ignored('apple'), isTrue);
       expect(filter.ignored('banana'), isTrue);
     });
+
     test('test_ignoreSubDir', () {
-      var filter = PathFilter(root('/'), ['apple/*'], context);
+      var filter = withSingleRoot(root('/'), ['apple/*']);
       expect(filter.ignored('apple/banana'), isTrue);
       expect(filter.ignored('apple/banana/cantaloupe'), isFalse);
     });
+
     test('test_ignoreTree', () {
-      var filter = PathFilter(root('/'), ['apple/**'], context);
+      var filter = withSingleRoot(root('/'), ['apple/**']);
       expect(filter.ignored('apple/banana'), isTrue);
       expect(filter.ignored('apple/banana/cantaloupe'), isTrue);
     });
+
     test('test_ignoreSdkExt', () {
-      var filter = PathFilter(root('/'), ['sdk_ext/**'], context);
+      var filter = withSingleRoot(root('/'), ['sdk_ext/**']);
       expect(filter.ignored('sdk_ext/entry.dart'), isTrue);
       expect(filter.ignored('sdk_ext/lib/src/part.dart'), isTrue);
     });
+
     test('test_outsideRoot', () {
-      var filter =
-          PathFilter(root('/workspace/dart/sdk'), ['sdk_ext/**'], context);
+      var filter = withSingleRoot(root('/workspace/dart/sdk'), ['sdk_ext/**']);
       expect(filter.ignored('/'), isTrue);
       expect(filter.ignored('/workspace'), isTrue);
       expect(filter.ignored('/workspace/dart'), isTrue);
       expect(filter.ignored('/workspace/dart/sdk'), isFalse);
       expect(filter.ignored('/workspace/dart/../dart/sdk'), isFalse);
     });
+
     test('test_relativePaths', () {
-      var filter =
-          PathFilter(root('/workspace/dart/sdk'), ['sdk_ext/**'], context);
+      var filter = withSingleRoot(root('/workspace/dart/sdk'), ['sdk_ext/**']);
       expect(filter.ignored('../apple'), isTrue);
       expect(filter.ignored('../sdk/main.dart'), isFalse);
       expect(filter.ignored('../sdk/sdk_ext/entry.dart'), isTrue);
     });
+
+    test('different ignore patterns root', () {
+      var filter = PathFilter(
+          root('/home/my'), root('/home'), ['my/test/ignored/*.dart'], context);
+      expect(filter.ignored(root('/home/my/lib/a.dart')), isFalse);
+      expect(filter.ignored(root('/home/my/test/ignored/b.dart')), isTrue);
+    });
   });
 }
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 3ef5b4d..44718305 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -301,7 +301,8 @@
   }
 
   test_class_alias_with_const_constructors() async {
-    addLibrarySource('/a.dart', '''
+    testFile = convertPath('/home/test/lib/test.dart');
+    addLibrarySource('/home/test/lib/a.dart', r'''
 class Base {
   const Base._priv();
   const Base();
@@ -316,7 +317,7 @@
     checkElementText(
         library,
         r'''
-import 'a.dart';
+import 'package:test/a.dart';
 class M {
 }
 class alias MixinApp extends Base with M {
@@ -324,16 +325,16 @@
     constantInitializers
       SuperConstructorInvocation
         argumentList: ArgumentList
-        staticElement: file:///a.dart::@class::Base::@constructor::•
+        staticElement: package:test/a.dart::@class::Base::@constructor::•
   synthetic const MixinApp.named();
     constantInitializers
       SuperConstructorInvocation
         argumentList: ArgumentList
         constructorName: SimpleIdentifier
-          staticElement: file:///a.dart::@class::Base::@constructor::named
+          staticElement: package:test/a.dart::@class::Base::@constructor::named
           staticType: null
           token: named
-        staticElement: file:///a.dart::@class::Base::@constructor::named
+        staticElement: package:test/a.dart::@class::Base::@constructor::named
 }
 ''',
         withFullyResolvedAst: true);
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index a9766f0..66b2f59 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -504,7 +504,16 @@
 
   /// TODO(scheglov) Use analyzedFiles()
   PathFilter get pathFilter {
-    return PathFilter(analysisContext.contextRoot.root.path,
+    var contextRoot = analysisContext.contextRoot;
+    var optionsFile = contextRoot.optionsFile;
+
+    // If there is no options file, there can be no excludes.
+    if (optionsFile == null) {
+      return PathFilter(contextRoot.root.path, contextRoot.root.path, []);
+    }
+
+    // Exclude patterns are relative to the directory with the options file.
+    return PathFilter(contextRoot.root.path, optionsFile.parent2.path,
         analysisContext.analysisOptions.excludePatterns);
   }
 
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/analysis_options.yaml b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/analysis_options.yaml
new file mode 100644
index 0000000..70da3f3
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/analysis_options.yaml
@@ -0,0 +1,3 @@
+analyzer:
+  exclude:
+    - inner/lib/excluded_error.dart
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/.dart_tool/package_config.json b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/.dart_tool/package_config.json
new file mode 100644
index 0000000..37ba145
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/.dart_tool/package_config.json
@@ -0,0 +1,11 @@
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "inner",
+      "rootUri": "../",
+      "packageUri": "lib",
+      "languageVersion": "2.9"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/excluded_error.dart b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/excluded_error.dart
new file mode 100644
index 0000000..b067213
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/excluded_error.dart
@@ -0,0 +1 @@
+ExcludedUndefinedClassInInner x = null;
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/not_excluded_error.dart b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/not_excluded_error.dart
new file mode 100644
index 0000000..dcceff1
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/lib/not_excluded_error.dart
@@ -0,0 +1 @@
+IncludedUndefinedClassInInner f = null;
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/pubspec.yaml b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/pubspec.yaml
new file mode 100644
index 0000000..d3320b8
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/inner/pubspec.yaml
@@ -0,0 +1,2 @@
+# Note `.dart_tool/package_config.json` - it, not this file, makes a new analysis context.
+name: inner
diff --git a/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/lib/not_excluded_error.dart b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/lib/not_excluded_error.dart
new file mode 100644
index 0000000..f271336
--- /dev/null
+++ b/pkg/analyzer_cli/test/data/exclude_portion_of_inner_context/lib/not_excluded_error.dart
@@ -0,0 +1,2 @@
+/// Should not be reported, we analyze only `inner`.
+IncludedUndefinedClassInOuter f = null;
diff --git a/pkg/analyzer_cli/test/driver_test.dart b/pkg/analyzer_cli/test/driver_test.dart
index d2a575b..c5e003e 100644
--- a/pkg/analyzer_cli/test/driver_test.dart
+++ b/pkg/analyzer_cli/test/driver_test.dart
@@ -396,6 +396,16 @@
     _expectUndefinedClassErrorsWithoutExclusions();
   }
 
+  Future<void> test_analysisOptions_excludes_inner() async {
+    await drive('data/exclude_portion_of_inner_context/inner',
+        options: 'data/exclude_portion_of_inner_context/$analysisOptionsYaml');
+    expect(
+      bulletToDash(outSink),
+      contains("error - Undefined class 'IncludedUndefinedClassInInner'"),
+    );
+    expect(outSink.toString(), contains('1 error found.'));
+  }
+
   Future<void>
       test_analysisOptions_excludesRelativeToAnalysisOptions_explicit() async {
     // The exclude is relative to the project, not/ the analyzed path, and it
diff --git a/tools/VERSION b/tools/VERSION
index 7944ac2..a7042d9 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 56
+PRERELEASE 57
 PRERELEASE_PATCH 0
\ No newline at end of file