analyzer: Convert applyOptions extension method into AnalysisOptionsBuilder builder.

Previously, the fields in an AnalysisOptionsImpl were primarily
arranged by an extension method, declared externally, called
`applyOptions`. It seemed very odd that this wasn't just a method on
AnalysisOptionsImpl; the "Impl" class is already private.

But it turns out that an AnalysisOptionsImpl is _mostly_ not mutated
in practice; the class wants to be immutable, with final fields.

In this change, I make AnalysisOptionsImpl mostly immutable, and
convert the `applyOptions` extension method, and all of its helper
code, into a builder class, AnalysisOptionsBuilder. While we don't
use a lot of builders in analyzer packages, this is a much more common
and idiomatic pattern for setting up complicated data, multiple values,
and then finalizing it all into an object that will not be changed
again during it's lifetime.

One big benefit of this refactoring is that most fields in AnalysisOptionsImpl are now final:

* sourceLanguageConstraint
* errorProcessors
* excludePatterns
* file
* strictCasts
* strictInference
* strictRawTypes
* chromeOsManifestChecks
* codeStyleOptions
* formatterOptions
* unignorableNames

Whereas before, they _all_ had to be mutable, as `applyOptions`, the
primary mechanism for setting up an AnalysisOptionsImpl, had to able
to write any field.

Other changes:

* Flip the instantiation of AnalysisOptionsImpl and CodeStyleOptions;
  now CodeStyleOptions is instatiated first, and AnalysisOptionsImpl
  sets itself as `codeStyleOptions.options`.
* Remove the private, deprecated, `applyToAnalysisOptions` function.

Change-Id: I6595d88aa5721e4f9a8b2b987482f9f43d27efd1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/390660
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/analysis_options/apply_options.dart b/pkg/analyzer/lib/src/analysis_options/apply_options.dart
deleted file mode 100644
index e571042..0000000
--- a/pkg/analyzer/lib/src/analysis_options/apply_options.dart
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright (c) 2023, 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/code_style_options.dart';
-import 'package:analyzer/dart/analysis/features.dart';
-import 'package:analyzer/dart/analysis/formatter_options.dart';
-import 'package:analyzer/error/error.dart';
-import 'package:analyzer/source/error_processor.dart';
-import 'package:analyzer/src/analysis_options/code_style_options.dart';
-import 'package:analyzer/src/dart/analysis/experiments.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/utilities_general.dart';
-import 'package:analyzer/src/lint/config.dart';
-import 'package:analyzer/src/lint/registry.dart';
-import 'package:analyzer/src/task/options.dart';
-import 'package:analyzer/src/util/yaml.dart';
-import 'package:yaml/yaml.dart';
-
-extension on YamlNode? {
-  bool? get boolValue {
-    var self = this;
-    if (self is YamlScalar) {
-      var value = self.value;
-      if (value is bool) {
-        return value;
-      }
-    }
-    return null;
-  }
-
-  String? get stringValue {
-    var self = this;
-    if (self is YamlScalar) {
-      var value = self.value;
-      if (value is String) {
-        return value;
-      }
-    }
-    return null;
-  }
-}
-
-extension on AnalysisOptionsImpl {
-  void applyExcludes(YamlNode? excludes) {
-    if (excludes is YamlList) {
-      // TODO(srawlins): Report non-String items
-      excludePatterns = excludes.whereType<String>().toList();
-    }
-    // TODO(srawlins): Report non-List with
-    // AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT.
-  }
-
-  void applyLanguageOptions(YamlNode? configs) {
-    if (configs is! YamlMap) {
-      return;
-    }
-    configs.nodes.forEach((key, value) {
-      if (key is YamlScalar && value is YamlScalar) {
-        var feature = key.value?.toString();
-        var boolValue = value.boolValue;
-        if (boolValue == null) {
-          return;
-        }
-
-        if (feature == AnalyzerOptions.strictCasts) {
-          strictCasts = boolValue;
-        }
-        if (feature == AnalyzerOptions.strictInference) {
-          strictInference = boolValue;
-        }
-        if (feature == AnalyzerOptions.strictRawTypes) {
-          strictRawTypes = boolValue;
-        }
-      }
-    });
-  }
-
-  void applyOptionalChecks(YamlNode? config) {
-    switch (config) {
-      case YamlMap():
-        for (var MapEntry(:key, :value) in config.nodes.entries) {
-          if (key is YamlScalar && value is YamlScalar) {
-            if (value.boolValue case var boolValue?) {
-              switch ('${key.value}') {
-                case AnalyzerOptions.chromeOsManifestChecks:
-                  chromeOsManifestChecks = boolValue;
-                case AnalyzerOptions.propagateLinterExceptions:
-                  propagateLinterExceptions = boolValue;
-              }
-            }
-          }
-        }
-      case YamlScalar():
-        switch ('${config.value}') {
-          case AnalyzerOptions.chromeOsManifestChecks:
-            chromeOsManifestChecks = true;
-          case AnalyzerOptions.propagateLinterExceptions:
-            propagateLinterExceptions = true;
-        }
-    }
-  }
-
-  void applyUnignorables(YamlNode? cannotIgnore) {
-    if (cannotIgnore is! YamlList) {
-      return;
-    }
-    var names = <String>{};
-    var stringValues = cannotIgnore.whereType<String>().toSet();
-    for (var severity in AnalyzerOptions.severities) {
-      if (stringValues.contains(severity)) {
-        // [severity] is a marker denoting all error codes with severity
-        // equal to [severity].
-        stringValues.remove(severity);
-        // Replace name like 'error' with error codes with this named
-        // severity.
-        for (var e in errorCodeValues) {
-          // If the severity of [error] is also changed in this options file
-          // to be [severity], we add [error] to the un-ignorable list.
-          var processors =
-              errorProcessors.where((processor) => processor.code == e.name);
-          if (processors.isNotEmpty &&
-              processors.first.severity?.displayName == severity) {
-            names.add(e.name);
-            continue;
-          }
-          // Otherwise, add [error] if its default severity is [severity].
-          if (e.errorSeverity.displayName == severity) {
-            names.add(e.name);
-          }
-        }
-      }
-    }
-    names.addAll(stringValues.map((name) => name.toUpperCase()));
-    unignorableNames = names;
-  }
-
-  CodeStyleOptions buildCodeStyleOptions(YamlNode? codeStyle) {
-    var useFormatter = false;
-    if (codeStyle is YamlMap) {
-      var formatNode = codeStyle.valueAt(AnalyzerOptions.format);
-      if (formatNode != null) {
-        var formatValue = toBool(formatNode);
-        if (formatValue is bool) {
-          useFormatter = formatValue;
-        }
-      }
-    }
-    return CodeStyleOptionsImpl(this, useFormatter: useFormatter);
-  }
-
-  FormatterOptions buildFormatterOptions(YamlNode? formatter) {
-    int? pageWidth;
-    if (formatter is YamlMap) {
-      var formatNode = formatter.valueAt(AnalyzerOptions.pageWidth);
-      var formatValue = formatNode?.value;
-      if (formatValue is int && formatValue > 0) {
-        pageWidth = formatValue;
-      }
-    }
-    return FormatterOptions(pageWidth: pageWidth);
-  }
-
-  void _applyLegacyPlugins(YamlNode? plugins) {
-    var pluginName = plugins.stringValue;
-    if (pluginName != null) {
-      enabledLegacyPluginNames = [pluginName];
-    } else if (plugins is YamlList) {
-      for (var element in plugins.nodes) {
-        var pluginName = element.stringValue;
-        if (pluginName != null) {
-          // Only the first legacy plugin is supported.
-          enabledLegacyPluginNames = [pluginName];
-          return;
-        }
-      }
-    } else if (plugins is YamlMap) {
-      for (var key in plugins.nodes.keys.cast<YamlNode?>()) {
-        var pluginName = key.stringValue;
-        if (pluginName != null) {
-          // Only the first legacy plugin is supported.
-          enabledLegacyPluginNames = [pluginName];
-          return;
-        }
-      }
-    }
-  }
-}
-
-extension AnalysisOptionsImplExtensions on AnalysisOptionsImpl {
-  /// Applies the options in the given [optionMap] to `this` analysis options.
-  void applyOptions(YamlMap? optionMap) {
-    if (optionMap == null) {
-      return;
-    }
-    var analyzer = optionMap.valueAt(AnalyzerOptions.analyzer);
-    if (analyzer is YamlMap) {
-      // Process filters.
-      var filters = analyzer.valueAt(AnalyzerOptions.errors);
-      errorProcessors = ErrorConfig(filters).processors;
-
-      // Process enabled experiments.
-      var experimentNames = analyzer.valueAt(AnalyzerOptions.enableExperiment);
-      if (experimentNames is YamlList) {
-        var enabledExperiments = <String>[];
-        for (var element in experimentNames.nodes) {
-          var experimentName = element.stringValue;
-          if (experimentName != null) {
-            enabledExperiments.add(experimentName);
-          }
-        }
-        contextFeatures = FeatureSet.fromEnableFlags2(
-          sdkLanguageVersion: ExperimentStatus.currentVersion,
-          flags: enabledExperiments,
-        );
-      }
-
-      // Process optional checks options.
-      var optionalChecks = analyzer.valueAt(AnalyzerOptions.optionalChecks);
-      applyOptionalChecks(optionalChecks);
-
-      // Process language options.
-      var language = analyzer.valueAt(AnalyzerOptions.language);
-      applyLanguageOptions(language);
-
-      // Process excludes.
-      var excludes = analyzer.valueAt(AnalyzerOptions.exclude);
-      applyExcludes(excludes);
-
-      var cannotIgnore = analyzer.valueAt(AnalyzerOptions.cannotIgnore);
-      applyUnignorables(cannotIgnore);
-
-      // Process plugins.
-      var plugins = analyzer.valueAt(AnalyzerOptions.plugins);
-      _applyLegacyPlugins(plugins);
-    }
-
-    // Process the 'code-style' option.
-    var codeStyle = optionMap.valueAt(AnalyzerOptions.codeStyle);
-    codeStyleOptions = buildCodeStyleOptions(codeStyle);
-
-    // Process the 'formatter' option.
-    var formatter = optionMap.valueAt(AnalyzerOptions.formatter);
-    formatterOptions = buildFormatterOptions(formatter);
-
-    var config = parseConfig(optionMap);
-    if (config != null) {
-      var enabledRules = Registry.ruleRegistry.enabled(config);
-      if (enabledRules.isNotEmpty) {
-        lint = true;
-        lintRules = enabledRules.toList();
-      }
-    }
-  }
-}
diff --git a/pkg/analyzer/lib/src/analysis_options/code_style_options.dart b/pkg/analyzer/lib/src/analysis_options/code_style_options.dart
index d5e1c51..ba5ed0c 100644
--- a/pkg/analyzer/lib/src/analysis_options/code_style_options.dart
+++ b/pkg/analyzer/lib/src/analysis_options/code_style_options.dart
@@ -9,12 +9,12 @@
 /// The concrete implementation of [CodeStyleOptions].
 class CodeStyleOptionsImpl implements CodeStyleOptions {
   /// The analysis options that owns this instance.
-  final AnalysisOptions _options;
+  late final AnalysisOptions options;
 
   @override
   final bool useFormatter;
 
-  CodeStyleOptionsImpl(this._options, {required this.useFormatter});
+  CodeStyleOptionsImpl({required this.useFormatter});
 
   @override
   bool get addTrailingCommas => _isLintEnabled('require_trailing_commas');
@@ -77,7 +77,7 @@
   }
 
   /// Returns whether the lint rule with the given [name] is enabled.
-  bool _isLintEnabled(String name) => _options.isLintEnabled(name);
+  bool _isLintEnabled(String name) => options.isLintEnabled(name);
 
   /// Returns the preferred lint quote, otherwise `null`.
   String? _lintQuote() => _isLintEnabled('prefer_single_quotes')
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
index f37b85a..043e0a7 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
@@ -8,7 +8,6 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
 import 'package:analyzer/src/context/builder.dart' show EmbedderYamlLocator;
 import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
@@ -119,20 +118,19 @@
     var optionsFile = contextRoot.optionsFile;
     var sourceFactory = workspace.createSourceFactory(sdk, summaryData);
 
-    var analysisOptionsMap =
-        // If there's an options file defined (as, e.g. passed into the
-        // AnalysisContextCollection), use a shared options map based on it.
-        (definedOptionsFile && optionsFile != null)
-            ? AnalysisOptionsMap.forSharedOptions(_getAnalysisOptions(
-                contextRoot,
-                optionsFile,
-                sourceFactory,
-                sdk,
-                updateAnalysisOptions2))
-            // Else, create one from the options file mappings stored in the
-            // context root.
-            : _createOptionsMap(
-                contextRoot, sourceFactory, updateAnalysisOptions2, sdk);
+    AnalysisOptionsMap analysisOptionsMap;
+    // If there's an options file defined (as, e.g. passed into the
+    // AnalysisContextCollection), use a shared options map based on it.
+    if (definedOptionsFile && optionsFile != null) {
+      analysisOptionsMap = AnalysisOptionsMap.forSharedOptions(
+          _getAnalysisOptions(contextRoot, optionsFile, sourceFactory, sdk,
+              updateAnalysisOptions2));
+    } else {
+      // Otherwise, create one from the options file mappings stored in the
+      // context root.
+      analysisOptionsMap = _createOptionsMap(
+          contextRoot, sourceFactory, updateAnalysisOptions2, sdk);
+    }
 
     var analysisContext =
         DriverBasedAnalysisContext(resourceProvider, contextRoot);
@@ -204,9 +202,9 @@
         (contextRoot as ContextRootImpl).optionsFileMap.entries;
     for (var entry in optionsMappings) {
       var file = entry.value;
-      var options = AnalysisOptionsImpl(file: file);
-      var optionsYaml = provider.getOptionsFromFile(file);
-      options.applyOptions(optionsYaml);
+      var options = AnalysisOptionsImpl.fromYaml(
+          file: file, optionsMap: provider.getOptionsFromFile(file));
+
       _optionsMap.add(entry.key, options);
     }
 
@@ -300,17 +298,20 @@
             required DartSdk sdk})?
         updateAnalysisOptions,
   ) {
-    var options = AnalysisOptionsImpl(file: optionsFile);
+    AnalysisOptionsImpl? options;
 
     if (optionsFile != null) {
       try {
         var provider = AnalysisOptionsProvider(sourceFactory);
-        var optionsMap = provider.getOptionsFromFile(optionsFile);
-        options.applyOptions(optionsMap);
+        options = AnalysisOptionsImpl.fromYaml(
+          optionsMap: provider.getOptionsFromFile(optionsFile),
+          file: optionsFile,
+        );
       } catch (e) {
-        // ignore
+        // Ignore exception.
       }
     }
+    options ??= AnalysisOptionsImpl(file: optionsFile);
 
     var pubspecFile = _findPubspecFile(contextRoot);
     if (pubspecFile != null) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
index a70e3eb..1057c68 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_locator.dart
@@ -10,7 +10,6 @@
 import 'package:analyzer/file_system/physical_file_system.dart'
     show PhysicalResourceProvider;
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
 import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/generated/engine.dart';
@@ -543,9 +542,10 @@
       var provider =
           AnalysisOptionsProvider(workspace.createSourceFactory(null, null));
 
-      var options = AnalysisOptionsImpl(file: optionsFile);
-      var optionsYaml = provider.getOptionsFromFile(optionsFile);
-      options.applyOptions(optionsYaml);
+      var options = AnalysisOptionsImpl.fromYaml(
+        file: optionsFile,
+        optionsMap: provider.getOptionsFromFile(optionsFile),
+      );
 
       return options.enabledLegacyPluginNames.toSet();
     } catch (_) {
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index c7cf089..8aaaf8d 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -12,10 +12,8 @@
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
-import 'package:analyzer/src/context/packages.dart';
+import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
 import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
 import 'package:analyzer/src/dart/analysis/cache.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart' show ErrorEncoding;
@@ -31,7 +29,8 @@
 import 'package:analyzer/src/dart/micro/analysis_context.dart';
 import 'package:analyzer/src/dart/micro/utils.dart';
 import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
-import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptionsImpl, AnalysisOptionsBuilder;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
 import 'package:analyzer/src/summary/format.dart';
@@ -726,10 +725,12 @@
       return;
     }
 
-    var analysisOptions = AnalysisOptionsImpl()
-      ..strictInference = fileAnalysisOptions.strictInference
-      ..contextFeatures = FeatureSet.latestLanguageVersion()
-      ..nonPackageFeatureSet = FeatureSet.latestLanguageVersion();
+    var analysisOptions = (AnalysisOptionsBuilder()
+          ..strictInference = fileAnalysisOptions.strictInference
+          ..contextFeatures =
+              FeatureSet.latestLanguageVersion() as ExperimentStatus
+          ..nonPackageFeatureSet = FeatureSet.latestLanguageVersion())
+        .build();
 
     if (fsState == null) {
       var featureSetProvider = FeatureSetProvider.build(
@@ -855,12 +856,14 @@
       }
     }
 
-    var options = AnalysisOptionsImpl();
+    late AnalysisOptionsImpl options;
 
-    if (optionMap != null) {
+    if (optionMap case YamlMap map) {
       performance.run('applyToAnalysisOptions', (_) {
-        options.applyOptions(optionMap!);
+        options = AnalysisOptionsImpl.fromYaml(optionsMap: map);
       });
+    } else {
+      options = AnalysisOptionsImpl();
     }
 
     if (isThirdParty) {
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index b250b78..fe4f6b5 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -19,10 +19,16 @@
 import 'package:analyzer/src/analysis_options/code_style_options.dart';
 import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/generated/source.dart' show SourceFactory;
+import 'package:analyzer/src/generated/utilities_general.dart' show toBool;
+import 'package:analyzer/src/lint/config.dart';
 import 'package:analyzer/src/lint/linter.dart';
+import 'package:analyzer/src/lint/registry.dart';
 import 'package:analyzer/src/summary/api_signature.dart';
+import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/util/yaml.dart';
 import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
 
 export 'package:analyzer/dart/analysis/analysis_options.dart';
 export 'package:analyzer/error/listener.dart' show RecordingErrorListener;
@@ -152,6 +158,208 @@
   AnalysisErrorInfoImpl(this.errors, this.lineInfo);
 }
 
+/// A builder for [AnalysisOptionsImpl].
+///
+/// To be used when an [AnalysisOptionsImpl] needs to be constructed, but with
+/// individually set options, rather than being built from YAML.
+final class AnalysisOptionsBuilder {
+  File? file;
+
+  ExperimentStatus contextFeatures = ExperimentStatus();
+
+  FeatureSet nonPackageFeatureSet = ExperimentStatus();
+
+  List<String> enabledLegacyPluginNames = const [];
+
+  List<ErrorProcessor> errorProcessors = [];
+
+  List<String> excludePatterns = [];
+
+  bool lint = false;
+
+  List<LintRule> lintRules = [];
+
+  bool propagateLinterExceptions = false;
+
+  bool strictCasts = false;
+
+  bool strictInference = false;
+
+  bool strictRawTypes = false;
+
+  bool chromeOsManifestChecks = false;
+
+  CodeStyleOptions codeStyleOptions = CodeStyleOptionsImpl(useFormatter: false);
+
+  FormatterOptions formatterOptions = FormatterOptions();
+
+  Set<String> unignorableNames = {};
+
+  AnalysisOptionsImpl build() {
+    return AnalysisOptionsImpl._(
+      file: file,
+      contextFeatures: contextFeatures,
+      nonPackageFeatureSet: nonPackageFeatureSet,
+      enabledLegacyPluginNames: enabledLegacyPluginNames,
+      errorProcessors: errorProcessors,
+      excludePatterns: excludePatterns,
+      lint: lint,
+      lintRules: lintRules,
+      propagateLinterExceptions: propagateLinterExceptions,
+      strictCasts: strictCasts,
+      strictInference: strictInference,
+      strictRawTypes: strictRawTypes,
+      chromeOsManifestChecks: chromeOsManifestChecks,
+      codeStyleOptions: codeStyleOptions,
+      formatterOptions: formatterOptions,
+      unignorableNames: unignorableNames,
+    );
+  }
+
+  void _applyCodeStyleOptions(YamlNode? codeStyle) {
+    var useFormatter = false;
+    if (codeStyle is YamlMap) {
+      var formatNode = codeStyle.valueAt(AnalyzerOptions.format);
+      if (formatNode != null) {
+        var formatValue = toBool(formatNode);
+        if (formatValue is bool) {
+          useFormatter = formatValue;
+        }
+      }
+    }
+    codeStyleOptions = CodeStyleOptionsImpl(useFormatter: useFormatter);
+  }
+
+  void _applyExcludes(YamlNode? excludes) {
+    if (excludes is YamlList) {
+      // TODO(srawlins): Report non-String items.
+      excludePatterns.addAll(excludes.whereType<String>());
+    }
+    // TODO(srawlins): Report non-List with
+    // AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT.
+  }
+
+  void _applyFormatterOptions(YamlNode? formatter) {
+    int? pageWidth;
+    if (formatter is YamlMap) {
+      var formatNode = formatter.valueAt(AnalyzerOptions.pageWidth);
+      var formatValue = formatNode?.value;
+      if (formatValue is int && formatValue > 0) {
+        pageWidth = formatValue;
+      }
+    }
+    formatterOptions = FormatterOptions(pageWidth: pageWidth);
+  }
+
+  void _applyLanguageOptions(YamlNode? configs) {
+    if (configs is! YamlMap) {
+      return;
+    }
+
+    configs.nodes.forEach((key, value) {
+      if (key is! YamlScalar || value is! YamlScalar) {
+        return;
+      }
+      var feature = key.value?.toString();
+      var boolValue = value.boolValue;
+      if (boolValue == null) {
+        return;
+      }
+
+      switch (feature) {
+        case AnalyzerOptions.strictCasts:
+          strictCasts = boolValue;
+        case AnalyzerOptions.strictInference:
+          strictInference = boolValue;
+        case AnalyzerOptions.strictRawTypes:
+          strictRawTypes = boolValue;
+      }
+    });
+  }
+
+  void _applyLegacyPlugins(YamlNode? plugins) {
+    var pluginName = plugins.stringValue;
+    if (pluginName != null) {
+      enabledLegacyPluginNames = [pluginName];
+    } else if (plugins is YamlList) {
+      for (var element in plugins.nodes) {
+        var pluginName = element.stringValue;
+        if (pluginName != null) {
+          // Only the first legacy plugin is supported.
+          enabledLegacyPluginNames = [pluginName];
+          return;
+        }
+      }
+    } else if (plugins is YamlMap) {
+      for (var key in plugins.nodes.keys.cast<YamlNode?>()) {
+        var pluginName = key.stringValue;
+        if (pluginName != null) {
+          // Only the first legacy plugin is supported.
+          enabledLegacyPluginNames = [pluginName];
+          return;
+        }
+      }
+    }
+  }
+
+  void _applyOptionalChecks(YamlNode? config) {
+    switch (config) {
+      case YamlMap():
+        for (var MapEntry(:key, :value) in config.nodes.entries) {
+          if (key is YamlScalar && value is YamlScalar) {
+            if (value.boolValue case var boolValue?) {
+              switch ('${key.value}') {
+                case AnalyzerOptions.chromeOsManifestChecks:
+                  chromeOsManifestChecks = boolValue;
+                case AnalyzerOptions.propagateLinterExceptions:
+                  propagateLinterExceptions = boolValue;
+              }
+            }
+          }
+        }
+      case YamlScalar():
+        switch ('${config.value}') {
+          case AnalyzerOptions.chromeOsManifestChecks:
+            chromeOsManifestChecks = true;
+          case AnalyzerOptions.propagateLinterExceptions:
+            propagateLinterExceptions = true;
+        }
+    }
+  }
+
+  void _applyUnignorables(YamlNode? cannotIgnore) {
+    if (cannotIgnore is! YamlList) {
+      return;
+    }
+    var stringValues = cannotIgnore.whereType<String>().toSet();
+    for (var severity in AnalyzerOptions.severities) {
+      if (stringValues.contains(severity)) {
+        // [severity] is a marker denoting all error codes with severity
+        // equal to [severity].
+        stringValues.remove(severity);
+        // Replace name like 'error' with error codes with this named
+        // severity.
+        for (var e in errorCodeValues) {
+          // If the severity of [error] is also changed in this options file
+          // to be [severity], we add [error] to the un-ignorable list.
+          var processors =
+              errorProcessors.where((processor) => processor.code == e.name);
+          if (processors.isNotEmpty &&
+              processors.first.severity?.displayName == severity) {
+            unignorableNames.add(e.name);
+            continue;
+          }
+          // Otherwise, add [error] if its default severity is [severity].
+          if (e.errorSeverity.displayName == severity) {
+            unignorableNames.add(e.name);
+          }
+        }
+      }
+    }
+    unignorableNames.addAll(stringValues.map((name) => name.toUpperCase()));
+  }
+}
+
 /// A set of analysis options used to control the behavior of an analysis
 /// context.
 class AnalysisOptionsImpl implements AnalysisOptions {
@@ -189,19 +397,19 @@
   FeatureSet nonPackageFeatureSet = ExperimentStatus();
 
   @override
-  List<String> enabledLegacyPluginNames = const <String>[];
+  List<String> enabledLegacyPluginNames;
 
   /// Return `true` if timing data should be gathered during execution.
   bool enableTiming = false;
 
   @override
-  List<ErrorProcessor> errorProcessors = [];
+  final List<ErrorProcessor> errorProcessors;
 
   @override
-  List<String> excludePatterns = [];
+  final List<String> excludePatterns;
 
   /// The associated `analysis_options.yaml` file (or `null` if there is none).
-  File? file;
+  final File? file;
 
   @override
   bool lint = false;
@@ -214,40 +422,135 @@
 
   /// Indicates whether linter exceptions should be propagated to the caller (by
   /// re-throwing them).
-  bool propagateLinterExceptions = false;
+  bool propagateLinterExceptions;
 
   /// Whether implicit casts should be reported as potential problems.
-  bool strictCasts = false;
+  final bool strictCasts;
 
   /// A flag indicating whether inference failures are allowed, off by default.
   ///
   /// This option is experimental and subject to change.
-  bool strictInference = false;
+  final bool strictInference;
 
   /// Whether raw types (types without explicit type arguments, such as `List`)
   /// should be reported as potential problems.
   ///
   /// Raw types are a common source of `dynamic` being introduced implicitly.
   /// This often leads to cast failures later on in the program.
-  bool strictRawTypes = false;
+  final bool strictRawTypes;
 
   @override
-  bool chromeOsManifestChecks = false;
+  final bool chromeOsManifestChecks;
 
   @override
-  late CodeStyleOptions codeStyleOptions;
+  final CodeStyleOptions codeStyleOptions;
 
   @override
-  FormatterOptions formatterOptions = FormatterOptions();
+  final FormatterOptions formatterOptions;
 
   /// The set of "un-ignorable" error names, as parsed from an analysis options
   /// file.
-  Set<String> unignorableNames = {};
+  // TODO(srawlins): Rename to `unignorableCodes`. 'Names' is ambiguous.
+  final Set<String> unignorableNames;
 
-  /// Initialize a newly created set of analysis options to have their default
-  /// values.
-  AnalysisOptionsImpl({this.file}) {
-    codeStyleOptions = CodeStyleOptionsImpl(this, useFormatter: false);
+  /// Returns a newly instantiated [AnalysisOptionsImpl].
+  ///
+  /// Optionally pass [file] as the file where the YAML can be found.
+  factory AnalysisOptionsImpl({File? file}) {
+    var builder = AnalysisOptionsBuilder()..file = file;
+    return builder.build();
+  }
+
+  /// Returns a newly instantiated [AnalysisOptionsImpl], as parsed from
+  /// [optionsMap].
+  ///
+  /// Optionally pass [file] as the file where the YAML can be found.
+  factory AnalysisOptionsImpl.fromYaml(
+      {required YamlMap optionsMap, File? file}) {
+    var builder = AnalysisOptionsBuilder()..file = file;
+
+    var analyzer = optionsMap.valueAt(AnalyzerOptions.analyzer);
+    if (analyzer is YamlMap) {
+      // Process filters.
+      var filters = analyzer.valueAt(AnalyzerOptions.errors);
+      builder.errorProcessors = ErrorConfig(filters).processors;
+
+      // Process enabled experiments.
+      var experimentNames = analyzer.valueAt(AnalyzerOptions.enableExperiment);
+      if (experimentNames is YamlList) {
+        var enabledExperiments = <String>[];
+        for (var element in experimentNames.nodes) {
+          var experimentName = element.stringValue;
+          if (experimentName != null) {
+            enabledExperiments.add(experimentName);
+          }
+        }
+        builder.contextFeatures = FeatureSet.fromEnableFlags2(
+          sdkLanguageVersion: ExperimentStatus.currentVersion,
+          flags: enabledExperiments,
+        ) as ExperimentStatus;
+        builder.nonPackageFeatureSet = builder.contextFeatures;
+      }
+
+      // Process optional checks options.
+      var optionalChecks = analyzer.valueAt(AnalyzerOptions.optionalChecks);
+      builder._applyOptionalChecks(optionalChecks);
+
+      // Process language options.
+      var language = analyzer.valueAt(AnalyzerOptions.language);
+      builder._applyLanguageOptions(language);
+
+      // Process excludes.
+      var excludes = analyzer.valueAt(AnalyzerOptions.exclude);
+      builder._applyExcludes(excludes);
+
+      var cannotIgnore = analyzer.valueAt(AnalyzerOptions.cannotIgnore);
+      builder._applyUnignorables(cannotIgnore);
+
+      // Process legacy plugins.
+      var legacyPlugins = analyzer.valueAt(AnalyzerOptions.plugins);
+      builder._applyLegacyPlugins(legacyPlugins);
+    }
+
+    // Process the 'formatter' option.
+    var formatter = optionsMap.valueAt(AnalyzerOptions.formatter);
+    builder._applyFormatterOptions(formatter);
+
+    var lintConfig = parseLintConfig(optionsMap);
+    if (lintConfig != null) {
+      var enabledRules = Registry.ruleRegistry.enabled(lintConfig);
+      if (enabledRules.isNotEmpty) {
+        builder.lint = true;
+        builder.lintRules = enabledRules.toList();
+      }
+    }
+
+    // Process the 'code-style' option.
+    var codeStyle = optionsMap.valueAt(AnalyzerOptions.codeStyle);
+    builder._applyCodeStyleOptions(codeStyle);
+
+    return builder.build();
+  }
+
+  AnalysisOptionsImpl._({
+    required this.file,
+    required ExperimentStatus contextFeatures,
+    required this.nonPackageFeatureSet,
+    required this.excludePatterns,
+    required this.enabledLegacyPluginNames,
+    required this.errorProcessors,
+    required this.lint,
+    required this.lintRules,
+    required this.propagateLinterExceptions,
+    required this.strictCasts,
+    required this.strictInference,
+    required this.strictRawTypes,
+    required this.chromeOsManifestChecks,
+    required this.codeStyleOptions,
+    required this.formatterOptions,
+    required this.unignorableNames,
+  }) : _contextFeatures = contextFeatures {
+    (codeStyleOptions as CodeStyleOptionsImpl).options = this;
   }
 
   @override
@@ -359,3 +662,27 @@
     return lintRules.any((rule) => rule.name == name);
   }
 }
+
+extension on YamlNode? {
+  bool? get boolValue {
+    var self = this;
+    if (self is YamlScalar) {
+      var value = self.value;
+      if (value is bool) {
+        return value;
+      }
+    }
+    return null;
+  }
+
+  String? get stringValue {
+    var self = this;
+    if (self is YamlScalar) {
+      var value = self.value;
+      if (value is String) {
+        return value;
+      }
+    }
+    return null;
+  }
+}
diff --git a/pkg/analyzer/lib/src/lint/analysis.dart b/pkg/analyzer/lib/src/lint/analysis.dart
index 4802ca5..1b6e93e 100644
--- a/pkg/analyzer/lib/src/lint/analysis.dart
+++ b/pkg/analyzer/lib/src/lint/analysis.dart
@@ -30,16 +30,6 @@
   io.exit(exitCode);
 }
 
-void _updateAnalyzerOptions(
-  AnalysisOptionsImpl analysisOptions,
-  LinterOptions options,
-) {
-  analysisOptions.lint = options.enableLints;
-  analysisOptions.warning = false;
-  analysisOptions.enableTiming = options.enableTiming;
-  analysisOptions.lintRules = options.enabledRules.toList(growable: false);
-}
-
 class DriverOptions {
   /// The maximum number of sources for which AST structures should be kept
   /// in the cache.  The default is 512.
@@ -92,7 +82,11 @@
         required contextRoot,
         required sdk,
       }) {
-        _updateAnalyzerOptions(analysisOptions, options);
+        analysisOptions.lint = options.enableLints;
+        analysisOptions.warning = false;
+        analysisOptions.enableTiming = options.enableTiming;
+        analysisOptions.lintRules =
+            options.enabledRules.toList(growable: false);
       },
     );
 
diff --git a/pkg/analyzer/lib/src/lint/config.dart b/pkg/analyzer/lib/src/lint/config.dart
index 0ac7d52..ed8ccba 100644
--- a/pkg/analyzer/lib/src/lint/config.dart
+++ b/pkg/analyzer/lib/src/lint/config.dart
@@ -7,7 +7,7 @@
 
 /// Parses [optionsMap] into a [LintConfig], returning the config, or `null` if
 /// [optionsMap] does not have `linter` map.
-LintConfig? parseConfig(YamlMap optionsMap) {
+LintConfig? parseLintConfig(YamlMap optionsMap) {
   var options = optionsMap.valueAt('linter');
   // Quick check of basic contract.
   if (options is YamlMap) {
@@ -24,7 +24,7 @@
   var yaml = loadYamlNode(fileContents,
       sourceUrl: fileUrl != null ? Uri.parse(fileUrl) : null);
   if (yaml is YamlMap) {
-    return parseConfig(yaml);
+    return parseLintConfig(yaml);
   }
   return null;
 }
diff --git a/pkg/analyzer/lib/src/task/options.dart b/pkg/analyzer/lib/src/task/options.dart
index bf87e60..1e32174 100644
--- a/pkg/analyzer/lib/src/task/options.dart
+++ b/pkg/analyzer/lib/src/task/options.dart
@@ -7,10 +7,8 @@
 import 'package:analyzer/source/error_processor.dart';
 import 'package:analyzer/source/source.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
 import 'package:analyzer/src/analysis_options/error/option_codes.dart';
 import 'package:analyzer/src/dart/analysis/experiments.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart' show SourceFactory;
 import 'package:analyzer/src/generated/utilities_general.dart';
 import 'package:analyzer/src/lint/options_rule_validator.dart';
@@ -192,12 +190,6 @@
   return errors;
 }
 
-@Deprecated("Use 'applyOptions' made available in "
-    "'package:analyzer/src/analysis_options/apply_options.dart'")
-void applyToAnalysisOptions(AnalysisOptionsImpl options, YamlMap optionMap) {
-  options.applyOptions(optionMap);
-}
-
 /// Returns the name of the first plugin, if one is specified in [options],
 /// otherwise `null`.
 String? _firstPluginName(YamlMap options) {
diff --git a/pkg/analyzer/test/source/error_processor_test.dart b/pkg/analyzer/test/source/error_processor_test.dart
index 972dc2a..fc54e97 100644
--- a/pkg/analyzer/test/source/error_processor_test.dart
+++ b/pkg/analyzer/test/source/error_processor_test.dart
@@ -5,7 +5,6 @@
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/source/error_processor.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:collection/collection.dart';
@@ -169,11 +168,11 @@
 }
 
 class _TestContext {
-  final analysisOptions = AnalysisOptionsImpl();
+  late AnalysisOptions analysisOptions;
 
   void configureOptions(String options) {
-    var optionMap = AnalysisOptionsProvider().getOptionsFromString(options);
-    analysisOptions.applyOptions(optionMap);
+    analysisOptions = AnalysisOptionsImpl.fromYaml(
+        optionsMap: AnalysisOptionsProvider().getOptionsFromString(options));
   }
 
   ErrorProcessor? getProcessor(AnalysisError error) {
diff --git a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
index d10c13c..1de3d64 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
@@ -13,7 +13,8 @@
 import 'package:analyzer/src/dart/analysis/context_root.dart';
 import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
 import 'package:analyzer/src/file_system/file_system.dart';
-import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptionsImpl, AnalysisOptionsBuilder;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/test_utilities/mock_sdk.dart';
@@ -83,7 +84,7 @@
         analysisContext.getAnalysisOptionsImplForFile(optionsFile);
     _expectEqualOptions(
       analysisOptions,
-      AnalysisOptionsImpl()..strictRawTypes = true,
+      (AnalysisOptionsBuilder()..strictRawTypes = true).build(),
     );
   }
 
diff --git a/pkg/analyzer/test/src/lint/config_test.dart b/pkg/analyzer/test/src/lint/config_test.dart
index 793c42d..671cd41 100644
--- a/pkg/analyzer/test/src/lint/config_test.dart
+++ b/pkg/analyzer/test/src/lint/config_test.dart
@@ -142,7 +142,7 @@
   group('options processing', () {
     group('raw maps', () {
       LintConfig parseMap(Map<Object, Object?> map) {
-        return parseConfig(wrap(map) as YamlMap)!;
+        return parseLintConfig(wrap(map) as YamlMap)!;
       }
 
       test('rule list', () {
diff --git a/pkg/analyzer/test/src/options/analysis_options_test.dart b/pkg/analyzer/test/src/options/analysis_options_test.dart
new file mode 100644
index 0000000..699c9a7
--- /dev/null
+++ b/pkg/analyzer/test/src/options/analysis_options_test.dart
@@ -0,0 +1,301 @@
+// Copyright (c) 2024, 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/error/error.dart';
+import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
+import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../generated/test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(AnalysisOptionsTest);
+  });
+}
+
+@reflectiveTest
+class AnalysisOptionsTest {
+  final AnalysisOptionsProvider optionsProvider = AnalysisOptionsProvider();
+
+  // TODO(srawlins): Add tests that exercise
+  // `optionsProvider.getOptionsFromString` throwing an exception.
+  AnalysisOptionsImpl parseOptions(String content) =>
+      AnalysisOptionsImpl.fromYaml(
+          optionsMap: optionsProvider.getOptionsFromString(content));
+
+  test_analyzer_cannotIgnore() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  cannot-ignore:
+    - one_error_code
+    - another
+''');
+
+    var unignorableNames = analysisOptions.unignorableNames;
+    expect(unignorableNames, unorderedEquals(['ONE_ERROR_CODE', 'ANOTHER']));
+  }
+
+  test_analyzer_cannotIgnore_severity() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  cannot-ignore:
+    - error
+''');
+
+    var unignorableNames = analysisOptions.unignorableNames;
+    expect(unignorableNames, contains('INVALID_ANNOTATION'));
+    expect(unignorableNames.length, greaterThan(500));
+  }
+
+  test_analyzer_cannotIgnore_severity_withProcessor() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  errors:
+    unused_import: error
+  cannot-ignore:
+    - error
+''');
+
+    var unignorableNames = analysisOptions.unignorableNames;
+    expect(unignorableNames, contains('UNUSED_IMPORT'));
+  }
+
+  test_analyzer_chromeos_checks() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+    chrome-os-manifest-checks
+''');
+    expect(analysisOptions.chromeOsManifestChecks, true);
+  }
+
+  test_analyzer_chromeos_checks_map() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+    chrome-os-manifest-checks : true
+''');
+    expect(analysisOptions.chromeOsManifestChecks, true);
+  }
+
+  test_analyzer_errors_cannotBeIgnoredByUniqueName() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  errors:
+    return_type_invalid_for_catch_error: ignore
+''');
+
+    var processors = analysisOptions.errorProcessors;
+    expect(processors, hasLength(1));
+
+    var warning = AnalysisError.tmp(
+      source: TestSource(),
+      offset: 0,
+      length: 1,
+      errorCode: WarningCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR,
+      arguments: [
+        ['x'],
+        ['y'],
+      ],
+    );
+
+    var processor = processors.first;
+    expect(processor.appliesTo(warning), isFalse);
+  }
+
+  test_analyzer_errors_severityIsError() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  errors:
+    unused_local_variable: error
+''');
+
+    var processors = analysisOptions.errorProcessors;
+    expect(processors, hasLength(1));
+
+    var warning = AnalysisError.tmp(
+      source: TestSource(),
+      offset: 0,
+      length: 1,
+      errorCode: WarningCode.UNUSED_LOCAL_VARIABLE,
+      arguments: [
+        ['x'],
+      ],
+    );
+
+    var processor = processors.first;
+    expect(processor.appliesTo(warning), isTrue);
+    expect(processor.severity, ErrorSeverity.ERROR);
+  }
+
+  test_analyzer_errors_severityIsIgnore() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  errors:
+    invalid_assignment: ignore
+''');
+
+    var processors = analysisOptions.errorProcessors;
+    expect(processors, hasLength(1));
+
+    var error = AnalysisError.tmp(
+      source: TestSource(),
+      offset: 0,
+      length: 1,
+      errorCode: CompileTimeErrorCode.INVALID_ASSIGNMENT,
+      arguments: [
+        ['x'],
+        ['y'],
+      ],
+    );
+
+    var processor = processors.first;
+    expect(processor.appliesTo(error), isTrue);
+    expect(processor.severity, isNull);
+  }
+
+  test_analyzer_errors_sharedNameAppliesToAllSharedCodes() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  errors:
+    invalid_return_type_for_catch_error: ignore
+''');
+
+    var processors = analysisOptions.errorProcessors;
+    expect(processors, hasLength(1));
+
+    var warning = AnalysisError.tmp(
+      source: TestSource(),
+      offset: 0,
+      length: 1,
+      errorCode: WarningCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR,
+      arguments: [
+        ['x'],
+        ['y'],
+      ],
+    );
+
+    var processor = processors.first;
+    expect(processor.appliesTo(warning), isTrue);
+    expect(processor.severity, isNull);
+  }
+
+  test_analyzer_exclude() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  exclude:
+    - foo/bar.dart
+    - 'test/**'
+''');
+
+    var excludes = analysisOptions.excludePatterns;
+    expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
+  }
+
+  test_analyzer_exclude_withNonStrings() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  exclude:
+    - foo/bar.dart
+    - 'test/**'
+    - a: b
+''');
+
+    var excludes = analysisOptions.excludePatterns;
+    expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
+  }
+
+  test_analyzer_legacyPlugins_list() {
+    // TODO(srawlins): Test legacy plugins as a list of non-scalar values
+    // (`- angular2: yes`).
+    var analysisOptions = parseOptions('''
+analyzer:
+  plugins:
+    - angular2
+    - intl
+''');
+
+    var names = analysisOptions.enabledLegacyPluginNames;
+    expect(names, ['angular2']);
+  }
+
+  test_analyzer_legacyPlugins_map() {
+    // TODO(srawlins): Test legacy plugins as a map of scalar values
+    // (`angular2: yes`).
+    var analysisOptions = parseOptions('''
+analyzer:
+  plugins:
+    angular2:
+      enabled: true
+''');
+
+    var names = analysisOptions.enabledLegacyPluginNames;
+    expect(names, ['angular2']);
+  }
+
+  test_analyzer_legacyPlugins_string() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  plugins:
+    angular2
+''');
+
+    var names = analysisOptions.enabledLegacyPluginNames;
+    expect(names, ['angular2']);
+  }
+
+  test_analyzer_optionalChecks_propagateLinterExceptions_default() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+''');
+    expect(analysisOptions.propagateLinterExceptions, false);
+  }
+
+  test_analyzer_optionalChecks_propagateLinterExceptions_empty() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+    propagate-linter-exceptions
+''');
+    expect(analysisOptions.propagateLinterExceptions, true);
+  }
+
+  test_analyzer_optionalChecks_propagateLinterExceptions_false() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+    propagate-linter-exceptions: false
+''');
+    expect(analysisOptions.propagateLinterExceptions, false);
+  }
+
+  test_analyzer_optionalChecks_propagateLinterExceptions_true() {
+    var analysisOptions = parseOptions('''
+analyzer:
+  optional-checks:
+    propagate-linter-exceptions: true
+''');
+    expect(analysisOptions.propagateLinterExceptions, true);
+  }
+
+  test_codeStyle_format_false() {
+    var analysisOptions = parseOptions('''
+code-style:
+  format: false
+''');
+    expect(analysisOptions.codeStyleOptions.useFormatter, false);
+  }
+
+  test_codeStyle_format_true() {
+    var analysisOptions = parseOptions('''
+code-style:
+  format: true
+''');
+    expect(analysisOptions.codeStyleOptions.useFormatter, true);
+  }
+}
diff --git a/pkg/analyzer/test/src/options/apply_options_test.dart b/pkg/analyzer/test/src/options/apply_options_test.dart
deleted file mode 100644
index 509d050..0000000
--- a/pkg/analyzer/test/src/options/apply_options_test.dart
+++ /dev/null
@@ -1,453 +0,0 @@
-// Copyright (c) 2023, 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/error/error.dart';
-import 'package:analyzer/source/error_processor.dart';
-import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/analysis_options/apply_options.dart';
-import 'package:analyzer/src/error/codes.dart';
-import 'package:analyzer/src/file_system/file_system.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/lint/linter.dart';
-import 'package:analyzer/src/lint/registry.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
-import 'package:test/test.dart';
-import 'package:test_reflective_loader/test_reflective_loader.dart';
-import 'package:yaml/yaml.dart';
-
-import '../../generated/test_support.dart';
-
-main() {
-  defineReflectiveSuite(() {
-    defineReflectiveTests(ApplyOptionsTest);
-    defineReflectiveTests(OptionsProviderTest);
-  });
-}
-
-@reflectiveTest
-class ApplyOptionsTest {
-  final AnalysisOptionsImpl analysisOptions = AnalysisOptionsImpl();
-
-  final AnalysisOptionsProvider optionsProvider = AnalysisOptionsProvider();
-
-  void configureContext(String optionsSource) =>
-      analysisOptions.applyOptions(parseOptions(optionsSource));
-
-  // TODO(srawlins): Add tests that exercise
-  // `optionsProvider.getOptionsFromString` throwing an exception.
-  YamlMap parseOptions(String content) =>
-      optionsProvider.getOptionsFromString(content);
-
-  test_analyzer_cannotIgnore() {
-    configureContext('''
-analyzer:
-  cannot-ignore:
-    - one_error_code
-    - another
-''');
-
-    var unignorableNames = analysisOptions.unignorableNames;
-    expect(unignorableNames, unorderedEquals(['ONE_ERROR_CODE', 'ANOTHER']));
-  }
-
-  test_analyzer_cannotIgnore_severity() {
-    configureContext('''
-analyzer:
-  cannot-ignore:
-    - error
-''');
-
-    var unignorableNames = analysisOptions.unignorableNames;
-    expect(unignorableNames, contains('INVALID_ANNOTATION'));
-    expect(unignorableNames.length, greaterThan(500));
-  }
-
-  test_analyzer_cannotIgnore_severity_withProcessor() {
-    configureContext('''
-analyzer:
-  errors:
-    unused_import: error
-  cannot-ignore:
-    - error
-''');
-
-    var unignorableNames = analysisOptions.unignorableNames;
-    expect(unignorableNames, contains('UNUSED_IMPORT'));
-  }
-
-  test_analyzer_chromeos_checks() {
-    configureContext('''
-analyzer:
-  optional-checks:
-    chrome-os-manifest-checks
-''');
-    expect(analysisOptions.chromeOsManifestChecks, true);
-  }
-
-  test_analyzer_chromeos_checks_map() {
-    configureContext('''
-analyzer:
-  optional-checks:
-    chrome-os-manifest-checks : true
-''');
-    expect(analysisOptions.chromeOsManifestChecks, true);
-  }
-
-  test_analyzer_errors_cannotBeIgnoredByUniqueName() {
-    configureContext('''
-analyzer:
-  errors:
-    return_type_invalid_for_catch_error: ignore
-''');
-
-    var processors = analysisOptions.errorProcessors;
-    expect(processors, hasLength(1));
-
-    var warning = AnalysisError.tmp(
-      source: TestSource(),
-      offset: 0,
-      length: 1,
-      errorCode: WarningCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR,
-      arguments: [
-        ['x'],
-        ['y'],
-      ],
-    );
-
-    var processor = processors.first;
-    expect(processor.appliesTo(warning), isFalse);
-  }
-
-  test_analyzer_errors_severityIsError() {
-    configureContext('''
-analyzer:
-  errors:
-    unused_local_variable: error
-''');
-
-    var processors = analysisOptions.errorProcessors;
-    expect(processors, hasLength(1));
-
-    var warning = AnalysisError.tmp(
-      source: TestSource(),
-      offset: 0,
-      length: 1,
-      errorCode: WarningCode.UNUSED_LOCAL_VARIABLE,
-      arguments: [
-        ['x'],
-      ],
-    );
-
-    var processor = processors.first;
-    expect(processor.appliesTo(warning), isTrue);
-    expect(processor.severity, ErrorSeverity.ERROR);
-  }
-
-  test_analyzer_errors_severityIsIgnore() {
-    configureContext('''
-analyzer:
-  errors:
-    invalid_assignment: ignore
-''');
-
-    var processors = analysisOptions.errorProcessors;
-    expect(processors, hasLength(1));
-
-    var error = AnalysisError.tmp(
-      source: TestSource(),
-      offset: 0,
-      length: 1,
-      errorCode: CompileTimeErrorCode.INVALID_ASSIGNMENT,
-      arguments: [
-        ['x'],
-        ['y'],
-      ],
-    );
-
-    var processor = processors.first;
-    expect(processor.appliesTo(error), isTrue);
-    expect(processor.severity, isNull);
-  }
-
-  test_analyzer_errors_sharedNameAppliesToAllSharedCodes() {
-    configureContext('''
-analyzer:
-  errors:
-    invalid_return_type_for_catch_error: ignore
-''');
-
-    var processors = analysisOptions.errorProcessors;
-    expect(processors, hasLength(1));
-
-    var warning = AnalysisError.tmp(
-      source: TestSource(),
-      offset: 0,
-      length: 1,
-      errorCode: WarningCode.RETURN_TYPE_INVALID_FOR_CATCH_ERROR,
-      arguments: [
-        ['x'],
-        ['y'],
-      ],
-    );
-
-    var processor = processors.first;
-    expect(processor.appliesTo(warning), isTrue);
-    expect(processor.severity, isNull);
-  }
-
-  test_analyzer_exclude() {
-    configureContext('''
-analyzer:
-  exclude:
-    - foo/bar.dart
-    - 'test/**'
-''');
-
-    var excludes = analysisOptions.excludePatterns;
-    expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
-  }
-
-  test_analyzer_exclude_withNonStrings() {
-    configureContext('''
-analyzer:
-  exclude:
-    - foo/bar.dart
-    - 'test/**'
-    - a: b
-''');
-
-    var excludes = analysisOptions.excludePatterns;
-    expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
-  }
-
-  test_analyzer_legacyPlugins_list() {
-    // TODO(srawlins): Test legacy plugins as a list of non-scalar values
-    // (`- angular2: yes`).
-    configureContext('''
-analyzer:
-  plugins:
-    - angular2
-    - intl
-''');
-
-    var names = analysisOptions.enabledLegacyPluginNames;
-    expect(names, ['angular2']);
-  }
-
-  test_analyzer_legacyPlugins_map() {
-    // TODO(srawlins): Test legacy plugins as a map of scalar values
-    // (`angular2: yes`).
-    configureContext('''
-analyzer:
-  plugins:
-    angular2:
-      enabled: true
-''');
-
-    var names = analysisOptions.enabledLegacyPluginNames;
-    expect(names, ['angular2']);
-  }
-
-  test_analyzer_legacyPlugins_string() {
-    configureContext('''
-analyzer:
-  plugins:
-    angular2
-''');
-
-    var names = analysisOptions.enabledLegacyPluginNames;
-    expect(names, ['angular2']);
-  }
-
-  test_analyzer_optionalChecks_propagateLinterExceptions_default() {
-    configureContext('''
-analyzer:
-  optional-checks:
-''');
-    expect(analysisOptions.propagateLinterExceptions, false);
-  }
-
-  test_analyzer_optionalChecks_propagateLinterExceptions_empty() {
-    configureContext('''
-analyzer:
-  optional-checks:
-    propagate-linter-exceptions
-''');
-    expect(analysisOptions.propagateLinterExceptions, true);
-  }
-
-  test_analyzer_optionalChecks_propagateLinterExceptions_false() {
-    configureContext('''
-analyzer:
-  optional-checks:
-    propagate-linter-exceptions: false
-''');
-    expect(analysisOptions.propagateLinterExceptions, false);
-  }
-
-  test_analyzer_optionalChecks_propagateLinterExceptions_true() {
-    configureContext('''
-analyzer:
-  optional-checks:
-    propagate-linter-exceptions: true
-''');
-    expect(analysisOptions.propagateLinterExceptions, true);
-  }
-
-  test_codeStyle_format_false() {
-    configureContext('''
-code-style:
-  format: false
-''');
-    expect(analysisOptions.codeStyleOptions.useFormatter, false);
-  }
-
-  test_codeStyle_format_true() {
-    configureContext('''
-code-style:
-  format: true
-''');
-    expect(analysisOptions.codeStyleOptions.useFormatter, true);
-  }
-}
-
-class ErrorProcessorMatcher extends Matcher {
-  final ErrorProcessor required;
-
-  ErrorProcessorMatcher(this.required);
-
-  @override
-  Description describe(Description desc) => desc
-    ..add("an ErrorProcessor setting ${required.code} to ${required.severity}");
-
-  @override
-  bool matches(dynamic o, Map<dynamic, dynamic> options) {
-    return o is ErrorProcessor &&
-        o.code.toUpperCase() == required.code.toUpperCase() &&
-        o.severity == required.severity;
-  }
-}
-
-@reflectiveTest
-class OptionsProviderTest with ResourceProviderMixin {
-  late final SourceFactory sourceFactory;
-
-  late final AnalysisOptionsProvider provider;
-
-  String get optionsFilePath => '/analysis_options.yaml';
-
-  void setUp() {
-    sourceFactory = SourceFactory([ResourceUriResolver(resourceProvider)]);
-    provider = AnalysisOptionsProvider(sourceFactory);
-  }
-
-  test_chooseFirstLegacyPlugin() {
-    newFile('/more_options.yaml', '''
-analyzer:
-  plugins:
-    - plugin_ddd
-    - plugin_ggg
-    - plugin_aaa
-''');
-    newFile('/other_options.yaml', '''
-include: more_options.yaml
-analyzer:
-  plugins:
-    - plugin_eee
-    - plugin_hhh
-    - plugin_bbb
-''');
-    newFile(optionsFilePath, r'''
-include: other_options.yaml
-analyzer:
-  plugins:
-    - plugin_fff
-    - plugin_iii
-    - plugin_ccc
-''');
-
-    var options = _getOptionsObject('/');
-    expect(options.enabledLegacyPluginNames, unorderedEquals(['plugin_ddd']));
-  }
-
-  test_mergeIncludedOptions() {
-    // TODO(srawlins): Split this into smaller tests.
-    // TODO(srawlins): add tests for multiple includes.
-    // TODO(srawlins): add tests with duplicate legacy plugin names.
-    // https://github.com/dart-lang/sdk/issues/50980
-
-    newFile('/other_options.yaml', '''
-analyzer:
-  exclude:
-    - toplevelexclude.dart
-  plugins:
-    toplevelplugin:
-      enabled: true
-  errors:
-    toplevelerror: warning
-linter:
-  rules:
-    - toplevellint
-''');
-    String code = r'''
-include: other_options.yaml
-analyzer:
-  exclude:
-    - lowlevelexclude.dart
-  errors:
-    lowlevelerror: warning
-linter:
-  rules:
-    - lowlevellint
-''';
-    newFile(optionsFilePath, code);
-
-    var lowlevellint = TestRule.withName('lowlevellint');
-    var toplevellint = TestRule.withName('toplevellint');
-    Registry.ruleRegistry.register(lowlevellint);
-    Registry.ruleRegistry.register(toplevellint);
-    var options = _getOptionsObject('/');
-
-    expect(options.lintRules, unorderedEquals([toplevellint, lowlevellint]));
-    expect(
-        options.enabledLegacyPluginNames, unorderedEquals(['toplevelplugin']));
-    expect(options.excludePatterns,
-        unorderedEquals(['toplevelexclude.dart', 'lowlevelexclude.dart']));
-    expect(
-        options.errorProcessors,
-        unorderedMatches([
-          ErrorProcessorMatcher(
-              ErrorProcessor('toplevelerror', ErrorSeverity.WARNING)),
-          ErrorProcessorMatcher(
-              ErrorProcessor('lowlevelerror', ErrorSeverity.WARNING))
-        ]));
-  }
-
-  AnalysisOptions _getOptionsObject(String posixPath) {
-    var map = provider.getOptions(getFolder(posixPath));
-    return AnalysisOptionsImpl()..applyOptions(map);
-  }
-}
-
-class TestRule extends LintRule {
-  static const LintCode code = LintCode(
-      'fantastic_test_rule', 'Fantastic test rule.',
-      correctionMessage: 'Try fantastic test rule.');
-
-  TestRule()
-      : super(
-          name: 'fantastic_test_rule',
-          description: '',
-        );
-
-  TestRule.withName(String name)
-      : super(
-          name: name,
-          description: '',
-        );
-
-  @override
-  LintCode get lintCode => code;
-}
diff --git a/pkg/analyzer/test/src/options/options_provider_test.dart b/pkg/analyzer/test/src/options/options_provider_test.dart
new file mode 100644
index 0000000..f7357e9
--- /dev/null
+++ b/pkg/analyzer/test/src/options/options_provider_test.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2024, 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/error/error.dart';
+import 'package:analyzer/source/error_processor.dart';
+import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
+import 'package:analyzer/src/file_system/file_system.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/lint/linter.dart';
+import 'package:analyzer/src/lint/registry.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(OptionsProviderTest);
+  });
+}
+
+class ErrorProcessorMatcher extends Matcher {
+  final ErrorProcessor required;
+
+  ErrorProcessorMatcher(this.required);
+
+  @override
+  Description describe(Description desc) => desc
+    ..add("an ErrorProcessor setting ${required.code} to ${required.severity}");
+
+  @override
+  bool matches(dynamic o, Map<dynamic, dynamic> options) {
+    return o is ErrorProcessor &&
+        o.code.toUpperCase() == required.code.toUpperCase() &&
+        o.severity == required.severity;
+  }
+}
+
+@reflectiveTest
+class OptionsProviderTest with ResourceProviderMixin {
+  late final SourceFactory sourceFactory;
+
+  late final AnalysisOptionsProvider provider;
+
+  String get optionsFilePath => '/analysis_options.yaml';
+
+  void setUp() {
+    sourceFactory = SourceFactory([ResourceUriResolver(resourceProvider)]);
+    provider = AnalysisOptionsProvider(sourceFactory);
+  }
+
+  test_chooseFirstLegacyPlugin() {
+    newFile('/more_options.yaml', '''
+analyzer:
+  plugins:
+    - plugin_ddd
+    - plugin_ggg
+    - plugin_aaa
+''');
+    newFile('/other_options.yaml', '''
+include: more_options.yaml
+analyzer:
+  plugins:
+    - plugin_eee
+    - plugin_hhh
+    - plugin_bbb
+''');
+    newFile(optionsFilePath, r'''
+include: other_options.yaml
+analyzer:
+  plugins:
+    - plugin_fff
+    - plugin_iii
+    - plugin_ccc
+''');
+
+    var options = _getOptionsObject('/');
+    expect(options.enabledLegacyPluginNames, unorderedEquals(['plugin_ddd']));
+  }
+
+  test_mergeIncludedOptions() {
+    // TODO(srawlins): Split this into smaller tests.
+    // TODO(srawlins): add tests for multiple includes.
+    // TODO(srawlins): add tests with duplicate legacy plugin names.
+    // https://github.com/dart-lang/sdk/issues/50980
+
+    newFile('/other_options.yaml', '''
+analyzer:
+  exclude:
+    - toplevelexclude.dart
+  plugins:
+    toplevelplugin:
+      enabled: true
+  errors:
+    toplevelerror: warning
+linter:
+  rules:
+    - toplevellint
+''');
+    String code = r'''
+include: other_options.yaml
+analyzer:
+  exclude:
+    - lowlevelexclude.dart
+  errors:
+    lowlevelerror: warning
+linter:
+  rules:
+    - lowlevellint
+''';
+    newFile(optionsFilePath, code);
+
+    var lowlevellint = TestRule.withName('lowlevellint');
+    var toplevellint = TestRule.withName('toplevellint');
+    Registry.ruleRegistry.register(lowlevellint);
+    Registry.ruleRegistry.register(toplevellint);
+    var options = _getOptionsObject('/');
+
+    expect(options.lintRules, unorderedEquals([toplevellint, lowlevellint]));
+    expect(
+        options.enabledLegacyPluginNames, unorderedEquals(['toplevelplugin']));
+    expect(options.excludePatterns,
+        unorderedEquals(['toplevelexclude.dart', 'lowlevelexclude.dart']));
+    expect(
+        options.errorProcessors,
+        unorderedMatches([
+          ErrorProcessorMatcher(
+              ErrorProcessor('toplevelerror', ErrorSeverity.WARNING)),
+          ErrorProcessorMatcher(
+              ErrorProcessor('lowlevelerror', ErrorSeverity.WARNING))
+        ]));
+  }
+
+  AnalysisOptions _getOptionsObject(String posixPath) =>
+      AnalysisOptionsImpl.fromYaml(
+          optionsMap: provider.getOptions(getFolder(posixPath)));
+}
+
+class TestRule extends LintRule {
+  static const LintCode code = LintCode(
+      'fantastic_test_rule', 'Fantastic test rule.',
+      correctionMessage: 'Try fantastic test rule.');
+
+  TestRule()
+      : super(
+          name: 'fantastic_test_rule',
+          description: '',
+        );
+
+  TestRule.withName(String name)
+      : super(
+          name: name,
+          description: '',
+        );
+
+  @override
+  LintCode get lintCode => code;
+}
diff --git a/pkg/analyzer/test/src/options/test_all.dart b/pkg/analyzer/test/src/options/test_all.dart
index 47c0eb5..677c301 100644
--- a/pkg/analyzer/test/src/options/test_all.dart
+++ b/pkg/analyzer/test/src/options/test_all.dart
@@ -4,12 +4,14 @@
 
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import 'apply_options_test.dart' as apply_options;
+import 'analysis_options_test.dart' as analysis_options;
+import 'options_provider_test.dart' as options_provider;
 import 'options_rule_validator_test.dart' as options_rule_validator;
 
 main() {
   defineReflectiveSuite(() {
-    apply_options.main();
+    analysis_options.main();
+    options_provider.main();
     options_rule_validator.main();
   }, name: 'options');
 }
diff --git a/pkg/analyzer_cli/lib/src/options.dart b/pkg/analyzer_cli/lib/src/options.dart
index 75a5995..73d0049 100644
--- a/pkg/analyzer_cli/lib/src/options.dart
+++ b/pkg/analyzer_cli/lib/src/options.dart
@@ -189,7 +189,7 @@
       analysisOptions.contextFeatures = FeatureSet.fromEnableFlags2(
         sdkLanguageVersion: ExperimentStatus.currentVersion,
         flags: enabledExperiments,
-      );
+      ) as ExperimentStatus;
     }
   }
 
diff --git a/pkg/analyzer_cli/test/options_test.dart b/pkg/analyzer_cli/test/options_test.dart
index 08945b2..293cf8c 100644
--- a/pkg/analyzer_cli/test/options_test.dart
+++ b/pkg/analyzer_cli/test/options_test.dart
@@ -9,7 +9,8 @@
 import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/dart/analysis/experiments_impl.dart'
     show overrideKnownFeatures;
-import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptionsImpl, AnalysisOptionsBuilder;
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer_cli/src/driver.dart';
 import 'package:analyzer_cli/src/options.dart';
@@ -291,7 +292,7 @@
   void test_updateAnalysisOptions_defaultLanguageVersion() {
     _applyAnalysisOptions(
       ['a.dart'],
-      (analysisOptions) {},
+      (_) {},
       (analysisOptions) {
         expect(
           analysisOptions.nonPackageLanguageVersion,
@@ -304,7 +305,7 @@
 
     _applyAnalysisOptions(
       ['--default-language-version=2.7', 'a.dart'],
-      (analysisOptions) {},
+      (_) {},
       (analysisOptions) {
         expect(
           analysisOptions.nonPackageLanguageVersion,
@@ -337,20 +338,19 @@
       releaseVersion: null,
     );
 
-    FeatureSet featuresWithExperiments(List<String> experiments) {
+    ExperimentStatus featuresWithExperiments(List<String> experiments) {
       return FeatureSet.fromEnableFlags2(
         sdkLanguageVersion: ExperimentStatus.currentVersion,
         flags: experiments,
-      );
+      ) as ExperimentStatus;
     }
 
     overrideKnownFeatures({'a': feature_a, 'b': feature_b}, () {
       // Replace.
       _applyAnalysisOptions(
         ['--enable-experiment=b', 'a.dart'],
-        (analysisOptions) {
-          analysisOptions.contextFeatures = featuresWithExperiments(['a']);
-        },
+        (optionsBuilder) =>
+            optionsBuilder.contextFeatures = featuresWithExperiments(['a']),
         (analysisOptions) {
           var featureSet = analysisOptions.contextFeatures;
           expect(featureSet.isEnabled(feature_a), isFalse);
@@ -361,9 +361,8 @@
       // Don't change if not provided.
       _applyAnalysisOptions(
         ['a.dart'],
-        (analysisOptions) {
-          analysisOptions.contextFeatures = featuresWithExperiments(['a']);
-        },
+        (optionsBuilder) =>
+            optionsBuilder.contextFeatures = featuresWithExperiments(['a']),
         (analysisOptions) {
           var featureSet = analysisOptions.contextFeatures;
           expect(featureSet.isEnabled(feature_a), isTrue);
@@ -375,14 +374,16 @@
 
   void _applyAnalysisOptions(
     List<String> args,
-    void Function(AnalysisOptionsImpl) configureInitial,
+    void Function(AnalysisOptionsBuilder) configureInitial,
     void Function(AnalysisOptionsImpl) checkApplied,
   ) {
     _parse(args);
     expect(commandLineOptions, isNotNull);
 
-    var analysisOptions = AnalysisOptionsImpl();
-    configureInitial(analysisOptions);
+    var optionsBuilder = AnalysisOptionsBuilder();
+    configureInitial(optionsBuilder);
+
+    var analysisOptions = optionsBuilder.build();
 
     commandLineOptions!.updateAnalysisOptions(analysisOptions);
     checkApplied(analysisOptions);
diff --git a/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart b/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
index 008a02e..c921be3 100644
--- a/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
+++ b/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
@@ -157,10 +157,10 @@
     ];
     var lineInfo = analyzer.LineInfo([0, 10, 20]);
     var severity = analyzer.ErrorSeverity.WARNING;
-    var options = analyzer.AnalysisOptionsImpl();
-    options.errorProcessors = [
-      analyzer.ErrorProcessor(analyzerErrors[0].errorCode.name, severity)
-    ];
+    var options = (analyzer.AnalysisOptionsBuilder()
+          ..errorProcessors.add(analyzer.ErrorProcessor(
+              analyzerErrors[0].errorCode.name, severity)))
+        .build();
 
     var pluginErrors = converter.convertAnalysisErrors(analyzerErrors,
         lineInfo: lineInfo, options: options);
@@ -189,10 +189,10 @@
       await createError(25),
     ];
     var severity = analyzer.ErrorSeverity.WARNING;
-    var options = analyzer.AnalysisOptionsImpl();
-    options.errorProcessors = [
-      analyzer.ErrorProcessor(analyzerErrors[0].errorCode.name, severity)
-    ];
+    var options = (analyzer.AnalysisOptionsBuilder()
+          ..errorProcessors.add(analyzer.ErrorProcessor(
+              analyzerErrors[0].errorCode.name, severity)))
+        .build();
 
     var pluginErrors =
         converter.convertAnalysisErrors(analyzerErrors, options: options);