Add support for providing fixes in analysis options files
Change-Id: I6c9a0a0624201538a90608c2eff9cff7b6cdb4b8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98870
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 6b7a5a9..12a1d18 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -22,6 +22,7 @@
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analysis_server/src/services/correction/organize_directives.dart';
import 'package:analysis_server/src/services/correction/sort_members.dart';
@@ -33,16 +34,23 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart' as engine;
+import 'package:analyzer/file_system/file_system.dart';
+// ignore: deprecated_member_use
+import 'package:analyzer/source/analysis_options_provider.dart';
+import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart' as engine;
import 'package:analyzer/src/error/codes.dart' as engine;
import 'package:analyzer/src/generated/engine.dart' as engine;
+import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/parser.dart' as engine;
import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/task/options.dart';
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:dart_style/dart_style.dart';
+import 'package:yaml/yaml.dart';
int test_resetCount = 0;
@@ -231,9 +239,7 @@
new EditGetDartfixInfoResult(allFixes.map((i) => i.asDartFix()).toList())
.toResponse(request.id);
- Future getFixes(Request request) async {
- // TODO(brianwilkerson) Determine whether this await is necessary.
- await null;
+ Future<void> getFixes(Request request) async {
EditGetFixesParams params = new EditGetFixesParams.fromRequest(request);
String file = params.file;
int offset = params.offset;
@@ -241,7 +247,6 @@
if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
return;
}
-
//
// Allow plugins to start computing fixes.
//
@@ -569,12 +574,49 @@
}
/**
- * Compute and return the fixes associated with server-generated errors.
+ * Compute and return the fixes associated with server-generated errors in
+ * analysis options files.
*/
- Future<List<AnalysisErrorFixes>> _computeServerErrorFixes(
+ Future<List<AnalysisErrorFixes>> _computeAnalysisOptionsFixes(
String file, int offset) async {
- // TODO(brianwilkerson) Determine whether this await is necessary.
- await null;
+ List<AnalysisErrorFixes> errorFixesList = <AnalysisErrorFixes>[];
+ File optionsFile = server.resourceProvider.getFile(file);
+ String content = _safelyRead(optionsFile);
+ if (content == null) {
+ return errorFixesList;
+ }
+ LineInfo lineInfo = new LineInfo.fromContent(content);
+ SourceFactory sourceFactory = server.getAnalysisDriver(file).sourceFactory;
+ List<engine.AnalysisError> errors = analyzeAnalysisOptions(
+ optionsFile.createSource(), content, sourceFactory);
+ YamlMap options = _getOptions(sourceFactory, content);
+ if (options == null) {
+ return errorFixesList;
+ }
+ for (engine.AnalysisError error in errors) {
+ AnalysisOptionsFixGenerator generator =
+ new AnalysisOptionsFixGenerator(error, content, options);
+ List<Fix> fixes = await generator.computeFixes();
+ if (fixes.isNotEmpty) {
+ fixes.sort(Fix.SORT_BY_RELEVANCE);
+ AnalysisError serverError =
+ newAnalysisError_fromEngine(lineInfo, error);
+ AnalysisErrorFixes errorFixes = new AnalysisErrorFixes(serverError);
+ errorFixesList.add(errorFixes);
+ fixes.forEach((fix) {
+ errorFixes.fixes.add(fix.change);
+ });
+ }
+ }
+ return errorFixesList;
+ }
+
+ /**
+ * Compute and return the fixes associated with server-generated errors in
+ * Dart files.
+ */
+ Future<List<AnalysisErrorFixes>> _computeDartFixes(
+ String file, int offset) async {
List<AnalysisErrorFixes> errorFixesList = <AnalysisErrorFixes>[];
var result = await server.getResolvedUnit(file);
if (result != null) {
@@ -603,6 +645,20 @@
return errorFixesList;
}
+ /**
+ * Compute and return the fixes associated with server-generated errors.
+ */
+ Future<List<AnalysisErrorFixes>> _computeServerErrorFixes(
+ String file, int offset) async {
+ if (AnalysisEngine.isDartFileName(file)) {
+ return _computeDartFixes(file, offset);
+ } else if (AnalysisEngine.isAnalysisOptionsFileName(
+ file, server.resourceProvider.pathContext)) {
+ return _computeAnalysisOptionsFixes(file, offset);
+ }
+ return <AnalysisErrorFixes>[];
+ }
+
Response _getAvailableRefactorings(Request request) {
_getAvailableRefactoringsImpl(request);
return Response.DELAYED_RESPONSE;
@@ -676,6 +732,16 @@
server.sendResponse(result.toResponse(request.id));
}
+ YamlMap _getOptions(SourceFactory sourceFactory, String content) {
+ AnalysisOptionsProvider optionsProvider =
+ new AnalysisOptionsProvider(sourceFactory);
+ try {
+ return optionsProvider.getOptionsFromString(content);
+ } on OptionsFormatException {
+ return null;
+ }
+ }
+
Response _getRefactoring(Request request) {
if (refactoringManager.hasPendingRequest) {
refactoringManager.cancel();
@@ -692,6 +758,16 @@
refactoringManager = new _RefactoringManager(server, refactoringWorkspace);
}
+ /// Return the contents of the [file], or `null` if the file does not exist or
+ /// cannot be read.
+ String _safelyRead(File file) {
+ try {
+ return file.readAsStringSync();
+ } on FileSystemException {
+ return null;
+ }
+ }
+
static int _getNumberOfScanParseErrors(List<engine.AnalysisError> errors) {
int numScanParseErrors = 0;
for (engine.AnalysisError error in errors) {
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index dbbc858..3c54bcb 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -11,9 +11,7 @@
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
-/**
- * Return true if this [errorCode] is likely to have a fix associated with it.
- */
+/// Return true if this [errorCode] is likely to have a fix associated with it.
bool hasFix(ErrorCode errorCode) =>
errorCode == StaticWarningCode.UNDEFINED_CLASS_BOOLEAN ||
errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER ||
@@ -103,9 +101,14 @@
errorCode.name == LintNames.unnecessary_this ||
errorCode.name == LintNames.use_rethrow_when_possible));
-/**
- * The implementation of [DartFixContext].
- */
+/// An enumeration of quick fix kinds for the errors found in an analysis
+/// options file.
+class AnalysisOptionsFixKind {
+ static const REMOVE_SETTING =
+ const FixKind('REMOVE_SETTING', 50, "Remove '{0}'");
+}
+
+/// The implementation of [DartFixContext].
class DartFixContextImpl implements DartFixContext {
@override
final ChangeWorkspace workspace;
@@ -119,9 +122,7 @@
DartFixContextImpl(this.workspace, this.resolveResult, this.error);
}
-/**
- * An enumeration of possible quick fix kinds.
- */
+/// An enumeration of quick fix kinds found in a Dart file.
class DartFixKind {
static const ADD_ASYNC =
const FixKind('ADD_ASYNC', 50, "Add 'async' modifier");
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/analysis_options/fix_generator.dart b/pkg/analysis_server/lib/src/services/correction/fix/analysis_options/fix_generator.dart
new file mode 100644
index 0000000..f558372
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/fix/analysis_options/fix_generator.dart
@@ -0,0 +1,238 @@
+// Copyright (c) 2019, 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 'dart:math' as math;
+
+import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/correction/strings.dart';
+import 'package:analyzer/error/error.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/analysis_options/error/option_codes.dart';
+import 'package:analyzer/src/generated/java_core.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart'
+ show SourceChange;
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:meta/meta.dart';
+import 'package:source_span/src/span.dart';
+import 'package:yaml/yaml.dart';
+
+/// The generator used to generate fixes in analysis options files.
+class AnalysisOptionsFixGenerator {
+ final AnalysisError error;
+
+ final int errorOffset;
+
+ final int errorLength;
+
+ final String content;
+
+ final YamlMap options;
+
+ final LineInfo lineInfo;
+
+ final List<Fix> fixes = <Fix>[];
+
+ List<YamlNode> coveringNodePath;
+
+ AnalysisOptionsFixGenerator(this.error, this.content, this.options)
+ : errorOffset = error.offset,
+ errorLength = error.length,
+ lineInfo = new LineInfo.fromContent(content);
+
+ /// Return the absolute, normalized path to the file in which the error was
+ /// reported.
+ String get file => error.source.fullName;
+
+ /// Return the list of fixes that apply to the error being fixed.
+ Future<List<Fix>> computeFixes() async {
+ YamlNodeLocator locator = new YamlNodeLocator(
+ start: errorOffset, end: errorOffset + errorLength - 1);
+ coveringNodePath = locator.searchWithin(options);
+ if (coveringNodePath.isEmpty) {
+ return fixes;
+ }
+
+ ErrorCode errorCode = error.errorCode;
+// if (errorCode == AnalysisOptionsErrorCode.INCLUDED_FILE_PARSE_ERROR) {
+// } else if (errorCode == AnalysisOptionsErrorCode.PARSE_ERROR) {
+// } else if (errorCode ==
+// AnalysisOptionsHintCode.DEPRECATED_ANALYSIS_OPTIONS_FILE_NAME) {
+// } else if (errorCode ==
+// AnalysisOptionsHintCode.PREVIEW_DART_2_SETTING_DEPRECATED) {
+// } else if (errorCode ==
+// AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED) {
+// } else
+ if (errorCode == AnalysisOptionsHintCode.SUPER_MIXINS_SETTING_DEPRECATED) {
+ await _addFix_removeSetting();
+// } else if (errorCode ==
+// AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED) {
+// } else if (errorCode == AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING) {
+// } else if (errorCode == AnalysisOptionsWarningCode.INCLUDE_FILE_NOT_FOUND) {
+// } else if (errorCode == AnalysisOptionsWarningCode.INVALID_OPTION) {
+// } else if (errorCode == AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT) {
+// } else if (errorCode == AnalysisOptionsWarningCode.SPEC_MODE_REMOVED) {
+// } else if (errorCode ==
+// AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE) {
+ } else if (errorCode ==
+ AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES) {
+ await _addFix_removeSetting();
+// } else if (errorCode ==
+// AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE) {
+// } else if (errorCode ==
+// AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES) {
+// } else if (errorCode == AnalysisOptionsWarningCode.UNSUPPORTED_VALUE) {
+ }
+ return fixes;
+ }
+
+ void _addFix_removeSetting() async {
+ if (coveringNodePath[0] is YamlScalar) {
+ SourceRange deletionRange;
+ int index = 1;
+ while (index < coveringNodePath.length) {
+ YamlNode parent = coveringNodePath[index];
+ if (parent is YamlList) {
+ if (parent.nodes.length > 1) {
+ YamlNode nodeToDelete = coveringNodePath[index - 1];
+ deletionRange = _lines(
+ nodeToDelete.span.start.offset, nodeToDelete.span.end.offset);
+ break;
+ }
+ } else if (parent is YamlMap) {
+ Map<dynamic, YamlNode> nodes = parent.nodes;
+ if (nodes.length > 1) {
+ YamlNode key;
+ YamlNode value;
+ YamlNode child = coveringNodePath[index - 1];
+ if (nodes.containsKey(child)) {
+ key = child;
+ value = nodes[child];
+ } else if (nodes.containsValue(child)) {
+ for (var entry in nodes.entries) {
+ if (child == entry.value) {
+ key = entry.key;
+ value = child;
+ break;
+ }
+ }
+ }
+ if (key == null || value == null) {
+ throw StateError(
+ 'Child is neither a key nor a value in the parent');
+ }
+ deletionRange = _lines(key.span.start.offset,
+ _firstNonWhitespaceBefore(value.span.end.offset));
+ break;
+ }
+ } else if (parent is YamlDocument) {
+ break;
+ }
+ index++;
+ }
+ YamlNode nodeToDelete = coveringNodePath[index - 1];
+ deletionRange ??=
+ _lines(nodeToDelete.span.start.offset, nodeToDelete.span.end.offset);
+ ChangeBuilder builder = new ChangeBuilder();
+ await builder.addFileEdit(file, (builder) {
+ builder.addDeletion(deletionRange);
+ });
+ _addFixFromBuilder(builder, AnalysisOptionsFixKind.REMOVE_SETTING,
+ args: [coveringNodePath[0].toString()]);
+ }
+ }
+
+ /// Add a fix whose edits were built by the [builder] that has the given
+ /// [kind]. If [args] are provided, they will be used to fill in the message
+ /// for the fix.
+ void _addFixFromBuilder(ChangeBuilder builder, FixKind kind,
+ {List args: null}) {
+ SourceChange change = builder.sourceChange;
+ if (change.edits.isEmpty) {
+ return;
+ }
+ change.message = formatList(kind.message, args);
+ fixes.add(new Fix(kind, change));
+ }
+
+ int _firstNonWhitespaceBefore(int offset) {
+ while (offset > 0 && isWhitespace(content.codeUnitAt(offset - 1))) {
+ offset--;
+ }
+ return offset;
+ }
+
+ SourceRange _lines(int start, int end) {
+ CharacterLocation startLocation = lineInfo.getLocation(start);
+ int startOffset = lineInfo.getOffsetOfLine(startLocation.lineNumber - 1);
+ CharacterLocation endLocation = lineInfo.getLocation(end);
+ int endOffset = lineInfo.getOffsetOfLine(
+ math.min(endLocation.lineNumber, lineInfo.lineCount - 1));
+ return new SourceRange(startOffset, endOffset - startOffset);
+ }
+}
+
+/// An object used to locate the [YamlNode] associated with a source range.
+/// More specifically, it will return the deepest [YamlNode] which completely
+/// encompasses the specified range.
+class YamlNodeLocator {
+ /// The inclusive start offset of the range used to identify the node.
+ int _startOffset = 0;
+
+ /// The inclusive end offset of the range used to identify the node.
+ int _endOffset = 0;
+
+ /// Initialize a newly created locator to locate the deepest [YamlNode] for
+ /// which `node.offset <= [start]` and `[end] < node.end`.
+ ///
+ /// If the [end] offset is not provided, then it is considered the same as the
+ /// [start] offset.
+ YamlNodeLocator({@required int start, int end})
+ : this._startOffset = start,
+ this._endOffset = end ?? start;
+
+ /// Search within the given Yaml [node] and return the path to the most deeply
+ /// nested node that includes the whole target range, or an empty list if no
+ /// node was found. The path is represented by all of the elements from the
+ /// starting [node] to the most deeply nested node, in reverse order.
+ List<YamlNode> searchWithin(YamlNode node) {
+ List<YamlNode> path = [];
+ _searchWithin(path, node);
+ return path;
+ }
+
+ void _searchWithin(List<YamlNode> path, YamlNode node) {
+ SourceSpan span = node.span;
+ if (span.start.offset > _endOffset || span.end.offset < _startOffset) {
+ return;
+ }
+ if (node is YamlList) {
+ for (YamlNode element in node.nodes) {
+ _searchWithin(path, element);
+ if (path.isNotEmpty) {
+ path.add(node);
+ return;
+ }
+ }
+ } else if (node is YamlMap) {
+ Map<dynamic, YamlNode> nodeMap = node.nodes;
+ for (YamlNode key in nodeMap.keys) {
+ _searchWithin(path, key);
+ if (path.isNotEmpty) {
+ path.add(node);
+ return;
+ }
+ _searchWithin(path, nodeMap[key]);
+ if (path.isNotEmpty) {
+ path.add(node);
+ return;
+ }
+ }
+ }
+ path.add(node);
+ }
+}
diff --git a/pkg/analysis_server/test/edit/test_all.dart b/pkg/analysis_server/test/edit/test_all.dart
index 79cc139..70251a6 100644
--- a/pkg/analysis_server/test/edit/test_all.dart
+++ b/pkg/analysis_server/test/edit/test_all.dart
@@ -4,24 +4,24 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
-import 'assists_test.dart' as assists_test;
-import 'fixes_test.dart' as fixes_test;
-import 'format_test.dart' as format_test;
-import 'organize_directives_test.dart' as organize_directives_test;
-import 'postfix_completion_test.dart' as postfix_completion_test;
-import 'refactoring_test.dart' as refactoring_test;
-import 'sort_members_test.dart' as sort_members_test;
-import 'statement_completion_test.dart' as statement_completion_test;
+import 'assists_test.dart' as assists;
+import 'fixes_test.dart' as fixes;
+import 'format_test.dart' as format;
+import 'organize_directives_test.dart' as organize_directives;
+import 'postfix_completion_test.dart' as postfix_completion;
+import 'refactoring_test.dart' as refactoring;
+import 'sort_members_test.dart' as sort_members;
+import 'statement_completion_test.dart' as statement_completion;
main() {
defineReflectiveSuite(() {
- assists_test.main();
- fixes_test.main();
- format_test.main();
- organize_directives_test.main();
- postfix_completion_test.main();
- refactoring_test.main();
- sort_members_test.main();
- statement_completion_test.main();
+ assists.main();
+ fixes.main();
+ format.main();
+ organize_directives.main();
+ postfix_completion.main();
+ refactoring.main();
+ sort_members.main();
+ statement_completion.main();
}, name: 'edit');
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/analysis_options/remove_setting_test.dart b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/remove_setting_test.dart
new file mode 100644
index 0000000..5a19c8b
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/remove_setting_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2019, 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:analysis_server/plugin/edit/fix/fix_core.dart';
+import 'package:analysis_server/src/protocol_server.dart' show SourceEdit;
+import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart';
+import 'package:analyzer/error/error.dart' as engine;
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/task/options.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart'
+ show SourceFileEdit;
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'package:yaml/src/yaml_node.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(RemoveSettingTest);
+ });
+}
+
+class NonDartFixTest with ResourceProviderMixin {
+ Future<void> assertHasFix(
+ String initialContent, String location, String expectedContent) async {
+ File optionsFile = resourceProvider.getFile('/analysis_options.yaml');
+ SourceFactory sourceFactory = new SourceFactory([]);
+ List<engine.AnalysisError> errors = analyzeAnalysisOptions(
+ optionsFile.createSource(), initialContent, sourceFactory);
+ expect(errors, hasLength(1));
+ engine.AnalysisError error = errors[0];
+ YamlMap options = _getOptions(sourceFactory, initialContent);
+ AnalysisOptionsFixGenerator generator =
+ new AnalysisOptionsFixGenerator(error, initialContent, options);
+ List<Fix> fixes = await generator.computeFixes();
+ expect(fixes, hasLength(1));
+ List<SourceFileEdit> fileEdits = fixes[0].change.edits;
+ expect(fileEdits, hasLength(1));
+
+ String actualContent =
+ SourceEdit.applySequence(initialContent, fileEdits[0].edits);
+ expect(actualContent, expectedContent);
+ }
+
+ YamlMap _getOptions(SourceFactory sourceFactory, String content) {
+ AnalysisOptionsProvider optionsProvider =
+ new AnalysisOptionsProvider(sourceFactory);
+ try {
+ return optionsProvider.getOptionsFromString(content);
+ } on OptionsFormatException {
+ return null;
+ }
+ }
+}
+
+@reflectiveTest
+class RemoveSettingTest extends NonDartFixTest {
+ test_enableSuperMixins() async {
+ await assertHasFix(
+ '''
+analyzer:
+ enable-experiment:
+ - non-nullable
+ language:
+ enableSuperMixins: true
+''',
+ 'enable',
+ '''
+analyzer:
+ enable-experiment:
+ - non-nullable
+''');
+ }
+
+ test_invalidExperiment_first() async {
+ await assertHasFix(
+ '''
+analyzer:
+ enable-experiment:
+ - not-an-experiment
+ - non-nullable
+''',
+ 'not-',
+ '''
+analyzer:
+ enable-experiment:
+ - non-nullable
+''');
+ }
+
+ test_invalidExperiment_last() async {
+ await assertHasFix(
+ '''
+analyzer:
+ enable-experiment:
+ - non-nullable
+ - not-an-experiment
+''',
+ 'not-',
+ '''
+analyzer:
+ enable-experiment:
+ - non-nullable
+''');
+ }
+
+ test_invalidExperiment_only() async {
+ await assertHasFix(
+ '''
+analyzer:
+ enable-experiment:
+ - not-an-experiment
+''',
+ 'not-',
+ '''
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_all.dart
new file mode 100644
index 0000000..c202ee9
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_all.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'remove_setting_test.dart' as remove_setting;
+
+main() {
+ defineReflectiveSuite(() {
+ remove_setting.main();
+ });
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index 801f95b..de8cd2a 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -23,6 +23,7 @@
import 'add_static_test.dart' as add_static;
import 'add_super_constructor_invocation_test.dart'
as add_super_constructor_invocation;
+import 'analysis_options/test_all.dart' as analysis_options;
import 'change_argument_name_test.dart' as change_argument_name;
import 'change_to_nearest_precise_value_test.dart'
as change_to_nearest_precise_value;
@@ -124,6 +125,7 @@
add_required.main();
add_static.main();
add_super_constructor_invocation.main();
+ analysis_options.main();
change_argument_name.main();
change_to.main();
change_to_nearest_precise_value.main();