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('''