bulk fix support for specified diagnostics
Change-Id: Ifa98da5466b0efb1b88cfa17d06b6b4e1d4ba8d3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255340
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index 7ce4cd6..7e52568 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -110,7 +110,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
- 1.33.1
+ 1.33.2
</h1>
<p>
This document contains a specification of the API provided by the
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 4867f3d..086d0c8 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
-const String PROTOCOL_VERSION = '1.33.1';
+const String PROTOCOL_VERSION = '1.33.2';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@@ -192,6 +192,7 @@
const String DIAGNOSTIC_RESPONSE_GET_DIAGNOSTICS_CONTEXTS = 'contexts';
const String DIAGNOSTIC_RESPONSE_GET_SERVER_PORT_PORT = 'port';
const String EDIT_REQUEST_BULK_FIXES = 'edit.bulkFixes';
+const String EDIT_REQUEST_BULK_FIXES_CODES = 'codes';
const String EDIT_REQUEST_BULK_FIXES_INCLUDED = 'included';
const String EDIT_REQUEST_BULK_FIXES_IN_TEST_MODE = 'inTestMode';
const String EDIT_REQUEST_FORMAT = 'edit.format';
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index 8b1d885..6748c45 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -5982,6 +5982,7 @@
/// {
/// "included": List<FilePath>
/// "inTestMode": optional bool
+/// "codes": optional List<String>
/// }
///
/// Clients may not extend, implement or mix-in this class.
@@ -6004,7 +6005,10 @@
/// If this field is omitted the flag defaults to false.
bool? inTestMode;
- EditBulkFixesParams(this.included, {this.inTestMode});
+ /// A list of diagnostic codes to be fixed.
+ List<String>? codes;
+
+ EditBulkFixesParams(this.included, {this.inTestMode, this.codes});
factory EditBulkFixesParams.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -6022,7 +6026,13 @@
inTestMode =
jsonDecoder.decodeBool('$jsonPath.inTestMode', json['inTestMode']);
}
- return EditBulkFixesParams(included, inTestMode: inTestMode);
+ List<String>? codes;
+ if (json.containsKey('codes')) {
+ codes = jsonDecoder.decodeList(
+ '$jsonPath.codes', json['codes'], jsonDecoder.decodeString);
+ }
+ return EditBulkFixesParams(included,
+ inTestMode: inTestMode, codes: codes);
} else {
throw jsonDecoder.mismatch(jsonPath, 'edit.bulkFixes params', json);
}
@@ -6041,6 +6051,10 @@
if (inTestMode != null) {
result['inTestMode'] = inTestMode;
}
+ var codes = this.codes;
+ if (codes != null) {
+ result['codes'] = codes;
+ }
return result;
}
@@ -6057,7 +6071,8 @@
if (other is EditBulkFixesParams) {
return listEqual(
included, other.included, (String a, String b) => a == b) &&
- inTestMode == other.inTestMode;
+ inTestMode == other.inTestMode &&
+ listEqual(codes, other.codes, (String a, String b) => a == b);
}
return false;
}
@@ -6066,6 +6081,7 @@
int get hashCode => Object.hash(
Object.hashAll(included),
inTestMode,
+ Object.hashAll(codes ?? []),
);
}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/edit_bulk_fixes.dart b/pkg/analysis_server/lib/src/handler/legacy/edit_bulk_fixes.dart
index 1ddaedd..5246a7f 100644
--- a/pkg/analysis_server/lib/src/handler/legacy/edit_bulk_fixes.dart
+++ b/pkg/analysis_server/lib/src/handler/legacy/edit_bulk_fixes.dart
@@ -38,7 +38,7 @@
var workspace = DartChangeWorkspace(
collection.contexts.map((c) => c.currentSession).toList());
var processor = BulkFixProcessor(server.instrumentationService, workspace,
- useConfigFiles: params.inTestMode ?? false);
+ useConfigFiles: params.inTestMode ?? false, codes: params.codes);
var changeBuilder = await processor.fixErrors(collection.contexts);
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index 372d262..ae8e9de 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -139,6 +139,9 @@
/// the transforms.
final bool useConfigFiles;
+ /// An optional list of diagnostic codes to fix.
+ final List<String>? codes;
+
/// The change builder used to build the changes required to fix the
/// diagnostics.
ChangeBuilder builder;
@@ -149,8 +152,9 @@
/// Initialize a newly created processor to create fixes for diagnostics in
/// libraries in the [workspace].
BulkFixProcessor(this.instrumentationService, this.workspace,
- {this.useConfigFiles = false})
- : builder = ChangeBuilder(workspace: workspace);
+ {this.useConfigFiles = false, List<String>? codes})
+ : builder = ChangeBuilder(workspace: workspace),
+ codes = codes?.map((e) => e.toLowerCase()).toList();
List<BulkFix> get fixDetails {
var details = <BulkFix>[];
@@ -221,8 +225,14 @@
Iterable<AnalysisError> filteredErrors(ResolvedUnitResult result) sync* {
var errors = result.errors.toList();
errors.sort((a, b) => a.offset.compareTo(b.offset));
- // Only fix errors not filtered out in analysis options.
+ final codes = this.codes;
+ // Only fix errors specified in the `codes` list (if defined) and not
+ // filtered out in analysis options.
for (var error in errors) {
+ if (codes != null &&
+ !codes.contains(error.errorCode.name.toLowerCase())) {
+ continue;
+ }
var processor = ErrorProcessor.getProcessor(analysisOptions, error);
if (processor == null || processor.severity != null) {
yield error;
diff --git a/pkg/analysis_server/test/edit/bulk_fixes_test.dart b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
index d3c47c2..ef67835 100644
--- a/pkg/analysis_server/test/edit/bulk_fixes_test.dart
+++ b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
@@ -14,49 +14,136 @@
void main() {
defineReflectiveSuite(() {
- defineReflectiveTests(BulkFixesTest);
+ defineReflectiveTests(BulkFixesFromOptionsTest);
+ defineReflectiveTests(BulkFixesFromCodesTest);
});
}
@reflectiveTest
-class BulkFixesTest extends PubPackageAnalysisServerTest {
- void assertContains(List<BulkFix> details,
- {required String path, required String code, required int count}) {
- for (var detail in details) {
- if (detail.path == path) {
- for (var fix in detail.fixes) {
- if (fix.code == code) {
- expect(fix.occurrences, count);
- return;
- }
- }
- }
- }
- fail('No match found for: $path:$code->$count in $details');
+class BulkFixesFromCodesTest extends BulkFixesTest {
+ Future<void> test_hint_checkWithNull() async {
+ addDiagnosticCode('TYPE_CHECK_WITH_NULL');
+ addTestFile('''
+void f(p, q) {
+ p is Null;
+ q is Null;
+}
+''');
+
+ await assertEditEquals(testFile, '''
+void f(p, q) {
+ p == null;
+ q == null;
+}
+''');
}
- Future<void> assertEditEquals(File file, String expectedSource) async {
- await waitForTasksFinished();
- var edits = await _getBulkEdits();
- expect(edits, hasLength(1));
- var editedSource =
- SourceEdit.applySequence(file.readAsStringSync(), edits[0].edits);
- expect(editedSource, expectedSource);
+ Future<void> test_hint_checkWithNull_notSpecified() async {
+ addDiagnosticCode('unnecessary_new');
+ addTestFile('''
+void f(p, q) {
+ p is Null;
+ q is Null;
+}
+''');
+
+ await assertNoEdits();
}
- Future<void> assertNoEdits() async {
- await waitForTasksFinished();
- var edits = await _getBulkEdits();
- expect(edits, isEmpty);
+ Future<void> test_hint_unusedImport() async {
+ addDiagnosticCode('unused_import');
+
+ newFile('$testPackageLibPath/a.dart', '');
+
+ addTestFile('''
+import 'a.dart';
+''');
+
+ var details = await _getBulkFixDetails();
+ expect(details, hasLength(1));
+ var fixes = details.first.fixes;
+ expect(fixes, hasLength(1));
+ var fix = fixes.first;
+ expect(fix.code, 'unused_import');
+ expect(fix.occurrences, 1);
}
- @override
- Future<void> setUp() async {
- super.setUp();
- registerLintRules();
- await setRoots(included: [workspaceRootPath], excluded: []);
+ Future<void> test_hint_unusedImport_notSpecified() async {
+ addDiagnosticCode('unnecessary_new');
+
+ newFile('$testPackageLibPath/a.dart', '');
+
+ addTestFile('''
+import 'a.dart';
+
+class A {
+ A f() => new A();
+}
+''');
+
+ var details = await _getBulkFixDetails();
+ expect(details, isEmpty);
}
+ Future<void> test_lint_unnecessaryNew() async {
+ newAnalysisOptionsYamlFile(testPackageRootPath, '''
+linter:
+ rules:
+ - annotate_overrides
+ - unnecessary_new
+''');
+ addDiagnosticCode('unnecessary_new');
+
+ addTestFile('''
+class A {
+ A f() => new A();
+}
+
+class B extends A {
+ A f() => new B();
+}
+''');
+
+ var details = await _getBulkFixDetails();
+ expect(details, hasLength(1));
+ var fixes = details.first.fixes;
+ expect(fixes, hasLength(1));
+ var fix = fixes.first;
+ expect(fix.code, 'unnecessary_new');
+ expect(fix.occurrences, 2);
+ }
+
+ Future<void> test_lint_unnecessaryNew_ignoreCase() async {
+ newAnalysisOptionsYamlFile(testPackageRootPath, '''
+linter:
+ rules:
+ - annotate_overrides
+ - unnecessary_new
+''');
+ addDiagnosticCode('UNNECESSARY_NEW');
+
+ addTestFile('''
+class A {
+ A f() => new A();
+}
+
+class B extends A {
+ A f() => new B();
+}
+''');
+
+ var details = await _getBulkFixDetails();
+ expect(details, hasLength(1));
+ var fixes = details.first.fixes;
+ expect(fixes, hasLength(1));
+ var fix = fixes.first;
+ expect(fix.code, 'unnecessary_new');
+ expect(fix.occurrences, 2);
+ }
+}
+
+@reflectiveTest
+class BulkFixesFromOptionsTest extends BulkFixesTest {
Future<void> test_annotateOverrides_excludedFile() async {
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
@@ -250,6 +337,52 @@
''');
await assertNoEdits();
}
+}
+
+abstract class BulkFixesTest extends PubPackageAnalysisServerTest {
+ List<String>? codes;
+
+ void addDiagnosticCode(String code) {
+ codes ??= <String>[];
+ codes!.add(code);
+ }
+
+ void assertContains(List<BulkFix> details,
+ {required String path, required String code, required int count}) {
+ for (var detail in details) {
+ if (detail.path == path) {
+ for (var fix in detail.fixes) {
+ if (fix.code == code) {
+ expect(fix.occurrences, count);
+ return;
+ }
+ }
+ }
+ }
+ fail('No match found for: $path:$code->$count in $details');
+ }
+
+ Future<void> assertEditEquals(File file, String expectedSource) async {
+ await waitForTasksFinished();
+ var edits = await _getBulkEdits();
+ expect(edits, hasLength(1));
+ var editedSource =
+ SourceEdit.applySequence(file.readAsStringSync(), edits[0].edits);
+ expect(editedSource, expectedSource);
+ }
+
+ Future<void> assertNoEdits() async {
+ await waitForTasksFinished();
+ var edits = await _getBulkEdits();
+ expect(edits, isEmpty);
+ }
+
+ @override
+ Future<void> setUp() async {
+ super.setUp();
+ registerLintRules();
+ await setRoots(included: [workspaceRootPath], excluded: []);
+ }
Future<List<SourceFileEdit>> _getBulkEdits() async {
var result = await _getBulkFixes();
@@ -262,7 +395,8 @@
}
Future<EditBulkFixesResult> _getBulkFixes() async {
- var request = EditBulkFixesParams([workspaceRoot.path]).toRequest('0');
+ var request =
+ EditBulkFixesParams([workspaceRoot.path], codes: codes).toRequest('0');
var response = await handleSuccessfulRequest(request);
return EditBulkFixesResult.fromResponse(response);
}
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index cbc2ec6..7910c35 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1735,6 +1735,10 @@
///
/// If this field is omitted the flag defaults to false.
///
+ /// codes: List<String> (optional)
+ ///
+ /// A list of diagnostic codes to be fixed.
+ ///
/// Returns
///
/// edits: List<SourceFileEdit>
@@ -1746,8 +1750,10 @@
/// Details that summarize the fixes associated with the recommended
/// changes.
Future<EditBulkFixesResult> sendEditBulkFixes(List<String> included,
- {bool? inTestMode}) async {
- var params = EditBulkFixesParams(included, inTestMode: inTestMode).toJson();
+ {bool? inTestMode, List<String>? codes}) async {
+ var params =
+ EditBulkFixesParams(included, inTestMode: inTestMode, codes: codes)
+ .toJson();
var result = await server.send('edit.bulkFixes', params);
var decoder = ResponseDecoder(null);
return EditBulkFixesResult.fromJson(decoder, 'result', result);
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index 6998661..c8b6c26 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -2305,10 +2305,11 @@
/// {
/// "included": List<FilePath>
/// "inTestMode": optional bool
+/// "codes": optional List<String>
/// }
final Matcher isEditBulkFixesParams = LazyMatcher(() => MatchesJsonObject(
'edit.bulkFixes params', {'included': isListOf(isFilePath)},
- optionalFields: {'inTestMode': isBool}));
+ optionalFields: {'inTestMode': isBool, 'codes': isListOf(isString)}));
/// edit.bulkFixes result
///
diff --git a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
index 63daf3e..89c2e07 100644
--- a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
+++ b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
@@ -517,8 +517,9 @@
* difference is that in test mode the fix processor will look for a configuration file that
* can modify the content of the data file used to compute the fixes when data-driven fixes
* are being considered. If this field is omitted the flag defaults to false.
+ * @param codes A list of diagnostic codes to be fixed.
*/
- public void edit_bulkFixes(List<String> included, boolean inTestMode, BulkFixesConsumer consumer);
+ public void edit_bulkFixes(List<String> included, boolean inTestMode, List<String> codes, BulkFixesConsumer consumer);
/**
* {@code edit.format}
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 0c14fe6..3bb1feb 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -7,7 +7,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
- <version>1.33.1</version>
+ <version>1.33.2</version>
</h1>
<p>
This document contains a specification of the API provided by the
@@ -2436,6 +2436,14 @@
If this field is omitted the flag defaults to <tt>false</tt>.
</p>
</field>
+ <field name="codes" optional="true">
+ <list>
+ <ref>String</ref>
+ </list>
+ <p>
+ A list of diagnostic codes to be fixed.
+ </p>
+ </field>
</params>
<result>
<field name="edits">
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 4867f3d..086d0c8 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -8,7 +8,7 @@
// ignore_for_file: constant_identifier_names
-const String PROTOCOL_VERSION = '1.33.1';
+const String PROTOCOL_VERSION = '1.33.2';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@@ -192,6 +192,7 @@
const String DIAGNOSTIC_RESPONSE_GET_DIAGNOSTICS_CONTEXTS = 'contexts';
const String DIAGNOSTIC_RESPONSE_GET_SERVER_PORT_PORT = 'port';
const String EDIT_REQUEST_BULK_FIXES = 'edit.bulkFixes';
+const String EDIT_REQUEST_BULK_FIXES_CODES = 'codes';
const String EDIT_REQUEST_BULK_FIXES_INCLUDED = 'included';
const String EDIT_REQUEST_BULK_FIXES_IN_TEST_MODE = 'inTestMode';
const String EDIT_REQUEST_FORMAT = 'edit.format';
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
index 7d49b78..94a247f 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
@@ -5982,6 +5982,7 @@
/// {
/// "included": List<FilePath>
/// "inTestMode": optional bool
+/// "codes": optional List<String>
/// }
///
/// Clients may not extend, implement or mix-in this class.
@@ -6004,7 +6005,10 @@
/// If this field is omitted the flag defaults to false.
bool? inTestMode;
- EditBulkFixesParams(this.included, {this.inTestMode});
+ /// A list of diagnostic codes to be fixed.
+ List<String>? codes;
+
+ EditBulkFixesParams(this.included, {this.inTestMode, this.codes});
factory EditBulkFixesParams.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -6022,7 +6026,13 @@
inTestMode =
jsonDecoder.decodeBool('$jsonPath.inTestMode', json['inTestMode']);
}
- return EditBulkFixesParams(included, inTestMode: inTestMode);
+ List<String>? codes;
+ if (json.containsKey('codes')) {
+ codes = jsonDecoder.decodeList(
+ '$jsonPath.codes', json['codes'], jsonDecoder.decodeString);
+ }
+ return EditBulkFixesParams(included,
+ inTestMode: inTestMode, codes: codes);
} else {
throw jsonDecoder.mismatch(jsonPath, 'edit.bulkFixes params', json);
}
@@ -6041,6 +6051,10 @@
if (inTestMode != null) {
result['inTestMode'] = inTestMode;
}
+ var codes = this.codes;
+ if (codes != null) {
+ result['codes'] = codes;
+ }
return result;
}
@@ -6057,7 +6071,8 @@
if (other is EditBulkFixesParams) {
return listEqual(
included, other.included, (String a, String b) => a == b) &&
- inTestMode == other.inTestMode;
+ inTestMode == other.inTestMode &&
+ listEqual(codes, other.codes, (String a, String b) => a == b);
}
return false;
}
@@ -6066,6 +6081,7 @@
int get hashCode => Object.hash(
Object.hashAll(included),
inTestMode,
+ Object.hashAll(codes ?? []),
);
}