Add code style options for code generation

Change-Id: Ibc5087aeb88965488502cca74923df6d7a2b185e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/238761
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
index 18f1072..46710be 100644
--- a/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
@@ -38,6 +38,9 @@
         AnalyzerOptions.implicitDynamic: EmptyProducer(),
       }),
     }),
+    AnalyzerOptions.codeStyle: MapProducer({
+      AnalyzerOptions.format: BooleanProducer(),
+    }),
     // TODO(brianwilkerson) Create a producer to produce `package:` URIs.
     AnalyzerOptions.include: EmptyProducer(),
     // TODO(brianwilkerson) Create constants for 'linter' and 'rules'.
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index a0e8c5c..816c714 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -1901,6 +1901,9 @@
         ..completion.isEqualTo('analyzer: ')
         ..kind.isIdentifier,
       (suggestion) => suggestion
+        ..completion.isEqualTo('code-style: ')
+        ..kind.isIdentifier,
+      (suggestion) => suggestion
         ..completion.isEqualTo('include: ')
         ..kind.isIdentifier,
       (suggestion) => suggestion
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart b/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart
index c86dc28..7d020ef 100644
--- a/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart
+++ b/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart
@@ -32,9 +32,27 @@
     assertSuggestion('${AnalyzerOptions.enableExperiment}: ');
   }
 
+  void test_codeStyle() {
+    getCompletions('''
+code-style:
+  ^
+''');
+    assertSuggestion('${AnalyzerOptions.format}: ');
+  }
+
+  void test_codeStyle_format() {
+    getCompletions('''
+code-style:
+  format: ^
+''');
+    assertSuggestion('false');
+    assertSuggestion('true');
+  }
+
   void test_empty() {
     getCompletions('^');
     assertSuggestion('${AnalyzerOptions.analyzer}: ');
+    assertSuggestion('${AnalyzerOptions.codeStyle}: ');
     assertSuggestion('${AnalyzerOptions.include}: ');
     // TODO(brianwilkerson) Replace this with a constant.
     assertSuggestion('linter: ');
diff --git a/pkg/analyzer/lib/dart/analysis/analysis_options.dart b/pkg/analyzer/lib/dart/analysis/analysis_options.dart
index 3f961bb..fbb185e 100644
--- a/pkg/analyzer/lib/dart/analysis/analysis_options.dart
+++ b/pkg/analyzer/lib/dart/analysis/analysis_options.dart
@@ -4,6 +4,7 @@
 
 import 'dart:typed_data';
 
+import 'package:analyzer/dart/analysis/code_style_options.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/source/error_processor.dart';
 import 'package:analyzer/src/services/lint.dart';
@@ -18,6 +19,9 @@
   /// see if it is complaint with Chrome OS.
   bool get chromeOsManifestChecks;
 
+  /// Return the options used to control the code that is generated.
+  CodeStyleOptions get codeStyleOptions;
+
   /// The set of features that are globally enabled for this context.
   FeatureSet get contextFeatures;
 
diff --git a/pkg/analyzer/lib/dart/analysis/code_style_options.dart b/pkg/analyzer/lib/dart/analysis/code_style_options.dart
new file mode 100644
index 0000000..4fb8a42
--- /dev/null
+++ b/pkg/analyzer/lib/dart/analysis/code_style_options.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, 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.
+
+/// A set of options related to coding style that apply to the code within a
+/// single analysis context.
+///
+/// Clients may not extend, implement or mix-in this class.
+abstract class CodeStyleOptions {
+  /// Return `true` if local variables should be `final` whenever possible.
+  bool get makeLocalsFinal;
+
+  /// Return `true` if the formatter should be used on code changes in this
+  /// context.
+  bool get useFormatter;
+
+  /// Return `true` if URIs should be "relative", meaning without a scheme,
+  /// whenever possible.
+  bool get useRelativeUris;
+}
diff --git a/pkg/analyzer/lib/src/analysis_options/code_style_options.dart b/pkg/analyzer/lib/src/analysis_options/code_style_options.dart
new file mode 100644
index 0000000..1276bd9
--- /dev/null
+++ b/pkg/analyzer/lib/src/analysis_options/code_style_options.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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/analysis_options.dart';
+import 'package:analyzer/dart/analysis/code_style_options.dart';
+
+/// The concrete implementation of [CodeStyleOptions].
+class CodeStyleOptionsImpl implements CodeStyleOptions {
+  /// The analysis options that owns this instance.
+  final AnalysisOptions options;
+
+  @override
+  final bool useFormatter;
+
+  CodeStyleOptionsImpl(this.options, {required this.useFormatter});
+
+  @override
+  bool get makeLocalsFinal => _isLintEnabled('prefer_final_locals');
+
+  @override
+  bool get useRelativeUris => _isLintEnabled('prefer_relative_imports');
+
+  /// Return `true` if the lint with the given [name] is enabled.
+  bool _isLintEnabled(String name) => options.isLintEnabled(name);
+}
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 6f6bf17..eb6df5c 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -6,10 +6,12 @@
 
 import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart';
 import 'package:analyzer/dart/analysis/analysis_options.dart';
+import 'package:analyzer/dart/analysis/code_style_options.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/instrumentation/instrumentation.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/constant.dart';
 import 'package:analyzer/src/generated/source.dart';
@@ -243,17 +245,23 @@
   @override
   bool chromeOsManifestChecks = false;
 
+  @override
+  late CodeStyleOptions codeStyleOptions;
+
   /// The set of "un-ignorable" error names, as parsed in [AnalyzerOptions] from
   /// an analysis options file.
   Set<String> unignorableNames = {};
 
   /// Initialize a newly created set of analysis options to have their default
   /// values.
-  AnalysisOptionsImpl();
+  AnalysisOptionsImpl() {
+    codeStyleOptions = CodeStyleOptionsImpl(this, useFormatter: false);
+  }
 
   /// Initialize a newly created set of analysis options to have the same values
   /// as those in the given set of analysis [options].
   AnalysisOptionsImpl.from(AnalysisOptions options) {
+    codeStyleOptions = options.codeStyleOptions;
     contextFeatures = options.contextFeatures;
     enabledPluginNames = options.enabledPluginNames;
     errorProcessors = options.errorProcessors;
diff --git a/pkg/analyzer/lib/src/task/options.dart b/pkg/analyzer/lib/src/task/options.dart
index 8170621..ea2f3f4 100644
--- a/pkg/analyzer/lib/src/task/options.dart
+++ b/pkg/analyzer/lib/src/task/options.dart
@@ -2,11 +2,13 @@
 // 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/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/source/error_processor.dart';
 import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
+import 'package:analyzer/src/analysis_options/code_style_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';
@@ -120,6 +122,7 @@
   static const String enablePreviewDart2 = 'enablePreviewDart2';
 
   static const String cannotIgnore = 'cannot-ignore';
+  static const String codeStyle = 'code-style';
   static const String enableExperiment = 'enable-experiment';
   static const String errors = 'errors';
   static const String exclude = 'exclude';
@@ -142,6 +145,9 @@
   static const String strictInference = 'strict-inference';
   static const String strictRawTypes = 'strict-raw-types';
 
+  // Code style options
+  static const String format = 'format';
+
   /// Ways to say `ignore`.
   static const List<String> ignoreSynonyms = ['ignore', 'false'];
 
@@ -188,6 +194,11 @@
     chromeOsManifestChecks,
   ];
 
+  /// Supported 'code-style' options.
+  static const List<String> codeStyleOptions = [
+    format,
+  ];
+
   /// Proposed values for a `true` or `false` option.
   static String get trueOrFalseProposal =>
       AnalyzerOptions.trueOrFalse.quotedAndCommaSeparatedWithAnd;
@@ -265,6 +276,61 @@
   }
 }
 
+/// Validates `code-style` options.
+class CodeStyleOptionsValidator extends OptionsValidator {
+  @override
+  void validate(ErrorReporter reporter, YamlMap options) {
+    var codeStyle = options.valueAt(AnalyzerOptions.codeStyle);
+    if (codeStyle is YamlMap) {
+      codeStyle.nodeMap.forEach((keyNode, valueNode) {
+        var key = keyNode.value;
+        if (key == AnalyzerOptions.format) {
+          _validateFormat(reporter, valueNode);
+        } else {
+          reporter.reportErrorForSpan(
+              AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES,
+              keyNode.span,
+              [AnalyzerOptions.codeStyle, keyNode.toString()]);
+        }
+      });
+    } else if (codeStyle is YamlScalar && codeStyle.value != null) {
+      reporter.reportErrorForSpan(
+          AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
+          codeStyle.span,
+          [AnalyzerOptions.codeStyle]);
+    } else if (codeStyle is YamlList) {
+      reporter.reportErrorForSpan(
+          AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
+          codeStyle.span,
+          [AnalyzerOptions.codeStyle]);
+    }
+  }
+
+  void _validateFormat(ErrorReporter reporter, YamlNode format) {
+    if (format is YamlMap) {
+      reporter.reportErrorForSpan(
+          AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
+          format.span,
+          [AnalyzerOptions.format]);
+    } else if (format is YamlScalar) {
+      var formatValue = toBool(format.value);
+      if (formatValue == null) {
+        reporter.reportErrorForSpan(
+            AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, format.span, [
+          AnalyzerOptions.format,
+          format.value,
+          AnalyzerOptions.trueOrFalseProposal
+        ]);
+      }
+    } else if (format is YamlList) {
+      reporter.reportErrorForSpan(
+          AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
+          format.span,
+          [AnalyzerOptions.format]);
+    }
+  }
+}
+
 /// Convenience class for composing validators.
 class CompositeValidator extends OptionsValidator {
   final List<OptionsValidator> validators;
@@ -540,6 +606,7 @@
 
   final List<OptionsValidator> _validators = [
     AnalyzerOptionsValidator(),
+    CodeStyleOptionsValidator(),
     LinterOptionsValidator(),
     LinterRuleOptionsValidator()
   ];
@@ -736,6 +803,10 @@
       options.enabledPluginNames = pluginNames;
     }
 
+    // Process the 'code-style' option.
+    var codeStyle = optionMap.valueAt(AnalyzerOptions.codeStyle);
+    options.codeStyleOptions = _buildCodeStyleOptions(options, codeStyle);
+
     var config = parseConfig(optionMap);
     if (config != null) {
       Iterable<LintRule> lintRules = Registry.ruleRegistry.enabled(config);
@@ -871,6 +942,21 @@
     }
   }
 
+  CodeStyleOptions _buildCodeStyleOptions(
+      AnalysisOptionsImpl options, YamlNode? config) {
+    var useFormatter = false;
+    if (config is YamlMap) {
+      var formatNode = config.valueAt(AnalyzerOptions.format);
+      if (formatNode != null) {
+        var formatValue = toBool(formatNode);
+        if (formatValue is bool) {
+          useFormatter = formatValue;
+        }
+      }
+    }
+    return CodeStyleOptionsImpl(options, useFormatter: useFormatter);
+  }
+
   String? _toString(YamlNode? node) {
     if (node is YamlScalar) {
       var value = node.value;
diff --git a/pkg/analyzer/test/src/task/options_test.dart b/pkg/analyzer/test/src/task/options_test.dart
index beadb3c..bf13737 100644
--- a/pkg/analyzer/test/src/task/options_test.dart
+++ b/pkg/analyzer/test/src/task/options_test.dart
@@ -43,7 +43,7 @@
   YamlMap parseOptions(String source) =>
       optionsProvider.getOptionsFromString(source);
 
-  test_configure_cannotIgnore() {
+  test_analyzer_cannotIgnore() {
     configureContext('''
 analyzer:
   cannot-ignore:
@@ -55,7 +55,7 @@
     expect(unignorableNames, unorderedEquals(['ONE_ERROR_CODE', 'ANOTHER']));
   }
 
-  test_configure_cannotIgnore_severity() {
+  test_analyzer_cannotIgnore_severity() {
     configureContext('''
 analyzer:
   cannot-ignore:
@@ -67,7 +67,7 @@
     expect(unignorableNames.length, greaterThan(500));
   }
 
-  test_configure_cannotIgnore_severity_withProcessor() {
+  test_analyzer_cannotIgnore_severity_withProcessor() {
     configureContext('''
 analyzer:
   errors:
@@ -80,7 +80,7 @@
     expect(unignorableNames, contains('UNUSED_IMPORT'));
   }
 
-  test_configure_chromeos_checks() {
+  test_analyzer_chromeos_checks() {
     configureContext('''
 analyzer:
   optional-checks:
@@ -89,7 +89,7 @@
     expect(true, analysisOptions.chromeOsManifestChecks);
   }
 
-  test_configure_chromeos_checks_map() {
+  test_analyzer_chromeos_checks_map() {
     configureContext('''
 analyzer:
   optional-checks:
@@ -98,7 +98,7 @@
     expect(true, analysisOptions.chromeOsManifestChecks);
   }
 
-  test_configure_error_processors() {
+  test_analyzer_errors_processors() {
     configureContext('''
 analyzer:
   errors:
@@ -106,7 +106,7 @@
     unused_local_variable: error
 ''');
 
-    List<ErrorProcessor> processors = analysisOptions.errorProcessors;
+    var processors = analysisOptions.errorProcessors;
     expect(processors, hasLength(2));
 
     var unused_local =
@@ -129,7 +129,7 @@
     expect(unusedLocal.severity, ErrorSeverity.ERROR);
   }
 
-  test_configure_excludes() {
+  test_analyzer_exclude() {
     configureContext('''
 analyzer:
   exclude:
@@ -137,11 +137,11 @@
     - 'test/**'
 ''');
 
-    List<String> excludes = analysisOptions.excludePatterns;
+    var excludes = analysisOptions.excludePatterns;
     expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
   }
 
-  test_configure_excludes_withNonStrings() {
+  test_analyzer_exclude_withNonStrings() {
     configureContext('''
 analyzer:
   exclude:
@@ -150,11 +150,11 @@
     - a: b
 ''');
 
-    List<String> excludes = analysisOptions.excludePatterns;
+    var excludes = analysisOptions.excludePatterns;
     expect(excludes, unorderedEquals(['foo/bar.dart', 'test/**']));
   }
 
-  test_configure_plugins_list() {
+  test_analyzer_plugins_list() {
     configureContext('''
 analyzer:
   plugins:
@@ -162,11 +162,11 @@
     - intl
 ''');
 
-    List<String> names = analysisOptions.enabledPluginNames;
+    var names = analysisOptions.enabledPluginNames;
     expect(names, ['angular2', 'intl']);
   }
 
-  test_configure_plugins_map() {
+  test_analyzer_plugins_map() {
     configureContext('''
 analyzer:
   plugins:
@@ -174,20 +174,36 @@
       enabled: true
 ''');
 
-    List<String> names = analysisOptions.enabledPluginNames;
+    var names = analysisOptions.enabledPluginNames;
     expect(names, ['angular2']);
   }
 
-  test_configure_plugins_string() {
+  test_analyzer_plugins_string() {
     configureContext('''
 analyzer:
   plugins:
     angular2
 ''');
 
-    List<String> names = analysisOptions.enabledPluginNames;
+    var names = analysisOptions.enabledPluginNames;
     expect(names, ['angular2']);
   }
+
+  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);
+  }
 }
 
 @reflectiveTest
@@ -529,6 +545,47 @@
 ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]);
   }
 
+  test_codeStyle_format_false() {
+    validate('''
+code-style:
+  format: false
+''', []);
+  }
+
+  test_codeStyle_format_invalid() {
+    validate('''
+code-style:
+  format: 80
+''', [AnalysisOptionsWarningCode.UNSUPPORTED_VALUE]);
+  }
+
+  test_codeStyle_format_true() {
+    validate('''
+code-style:
+  format: true
+''', []);
+  }
+
+  test_codeStyle_unsupported_list() {
+    validate('''
+code-style:
+  - format
+''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]);
+  }
+
+  test_codeStyle_unsupported_scalar() {
+    validate('''
+code-style: format
+''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]);
+  }
+
+  test_codeStyle_unsupportedOption() {
+    validate('''
+code-style:
+  not_supported: true
+''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES]);
+  }
+
   test_linter_supported_rules() {
     Registry.ruleRegistry.register(TestRule());
     validate('''