Skip to content

Commit 77961ae

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Adds fix for multiple_combinators warning
Bug: #56879 Fixes: #60309 Change-Id: Ifac77bbcb0d22c96cc01ea12fba1af86721809e2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/414800 Reviewed-by: Phil Quitslund <[email protected]> Auto-Submit: Felipe Morschel <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent df120f1 commit 77961ae

File tree

7 files changed

+816
-17
lines changed

7 files changed

+816
-17
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/fix.dart';
6+
import 'package:analysis_server/src/services/correction/util.dart';
7+
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
8+
import 'package:analyzer/dart/ast/token.dart';
9+
import 'package:analyzer/dart/element/element2.dart';
10+
import 'package:analyzer/src/dart/ast/ast.dart';
11+
import 'package:analyzer/src/dart/element/element.dart';
12+
import 'package:analyzer/src/dart/resolver/scope.dart';
13+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
14+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
15+
import 'package:analyzer_plugin/utilities/range_factory.dart';
16+
import 'package:collection/collection.dart';
17+
18+
class MergeCombinators extends MultiCorrectionProducer {
19+
MergeCombinators({required super.context});
20+
21+
@override
22+
Future<List<ResolvedCorrectionProducer>> get producers async {
23+
var node = this.node;
24+
NamespaceDirective? directive;
25+
if (node case Combinator(:NamespaceDirective parent)) {
26+
directive = parent;
27+
} else if (node is NamespaceDirective) {
28+
directive = node;
29+
} else {
30+
return const [];
31+
}
32+
33+
var combinators = directive.combinators;
34+
if (combinators.length < 2) {
35+
return const [];
36+
}
37+
38+
if (combinators.whereType<ShowCombinator>().isEmpty) {
39+
return [
40+
_MergeCombinators(
41+
DartFixKind.MERGE_COMBINATORS_HIDE_HIDE,
42+
directive,
43+
mergeWithShow: false,
44+
context: context,
45+
),
46+
_MergeCombinators(
47+
DartFixKind.MERGE_COMBINATORS_SHOW_HIDE,
48+
directive,
49+
mergeWithShow: true,
50+
context: context,
51+
),
52+
];
53+
}
54+
55+
return [
56+
_MergeCombinators(
57+
DartFixKind.MERGE_COMBINATORS_SHOW_SHOW,
58+
directive,
59+
mergeWithShow: true,
60+
context: context,
61+
),
62+
_MergeCombinators(
63+
DartFixKind.MERGE_COMBINATORS_HIDE_SHOW,
64+
directive,
65+
mergeWithShow: false,
66+
context: context,
67+
),
68+
];
69+
}
70+
}
71+
72+
class _MergeCombinators extends ResolvedCorrectionProducer {
73+
static final namespaceBuilder = NamespaceBuilder();
74+
75+
@override
76+
final FixKind fixKind;
77+
78+
final bool mergeWithShow;
79+
final NamespaceDirective directive;
80+
81+
_MergeCombinators(
82+
this.fixKind,
83+
this.directive, {
84+
required this.mergeWithShow,
85+
required super.context,
86+
});
87+
88+
@override
89+
CorrectionApplicability get applicability =>
90+
CorrectionApplicability.singleLocation;
91+
92+
@override
93+
Future<void> compute(ChangeBuilder builder) async {
94+
LibraryElement2? element;
95+
Map<String, Element2> namespace;
96+
Namespace? originalNamespace;
97+
switch (directive) {
98+
case ExportDirective(:LibraryExportElementImpl libraryExport):
99+
element = libraryExport.exportedLibrary2;
100+
if (element is! LibraryElementImpl) {
101+
return;
102+
}
103+
originalNamespace = _originalNamespace(element);
104+
namespace = _currentNamespace(libraryExport).definedNames2;
105+
case ImportDirective(:var libraryImport?):
106+
namespace = getImportNamespace(libraryImport);
107+
element = libraryImport.importedLibrary2;
108+
default:
109+
return;
110+
}
111+
if (element is! LibraryElementImpl) {
112+
return;
113+
}
114+
115+
if (mergeWithShow) {
116+
var referencedNames = namespace.keys.toList();
117+
await _buildNewCombinator(builder, Keyword.SHOW, referencedNames);
118+
return;
119+
}
120+
121+
originalNamespace ??= _originalNamespace(element);
122+
123+
var explicitlyHiddenNames = directive.hideCombinators.hiddenNames;
124+
125+
var hiddenNames =
126+
{
127+
...explicitlyHiddenNames,
128+
...originalNamespace.hiddenNames({
129+
...explicitlyHiddenNames,
130+
...namespace.keys,
131+
}),
132+
}.toList();
133+
await _buildNewCombinator(builder, Keyword.HIDE, hiddenNames);
134+
}
135+
136+
Future<void> _buildNewCombinator(
137+
ChangeBuilder builder,
138+
Keyword keyword,
139+
List<String> names,
140+
) async {
141+
if (getCodeStyleOptions(unitResult.file).sortCombinators) {
142+
names.sort();
143+
}
144+
await builder.addDartFileEdit(file, (builder) {
145+
var combinator = '';
146+
if (names.isNotEmpty) {
147+
combinator = ' ${keyword.lexeme} ${names.join(', ')}';
148+
}
149+
var combinators = directive.combinators;
150+
builder.addSimpleReplacement(
151+
range.startOffsetEndOffset(combinators.offset - 1, combinators.end),
152+
combinator,
153+
);
154+
});
155+
}
156+
157+
Namespace _currentNamespace(LibraryExportElementImpl libraryExport) {
158+
return namespaceBuilder.createExportNamespaceForDirective2(libraryExport);
159+
}
160+
161+
Namespace _originalNamespace(LibraryElementImpl element) {
162+
return namespaceBuilder.createExportNamespaceForLibrary(element);
163+
}
164+
}
165+
166+
extension on Namespace {
167+
Iterable<String> hiddenNames(Set<String> currentNames) {
168+
return definedNames2.keys.whereNot(currentNames.contains);
169+
}
170+
}
171+
172+
extension on NodeList<Combinator> {
173+
int get end => endToken!.end;
174+
int get offset => beginToken!.offset;
175+
}
176+
177+
extension on NamespaceDirective {
178+
Iterable<HideCombinator> get hideCombinators {
179+
return combinators.whereType<HideCombinator>();
180+
}
181+
}
182+
183+
extension on Iterable<HideCombinator> {
184+
Set<String> get hiddenNames {
185+
var set = <String>{};
186+
for (var combinator in this) {
187+
set.addAll(combinator.hiddenNames.map((identifier) => identifier.name));
188+
}
189+
return set;
190+
}
191+
}

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3657,9 +3657,7 @@ WarningCode.MIXIN_ON_SEALED_CLASS:
36573657
`sealed` annotation is likely to be replaced by the `sealed` keyword
36583658
in the next stable release.
36593659
WarningCode.MULTIPLE_COMBINATORS:
3660-
status: needsFix
3661-
notes: |-
3662-
Aggregate the combinators into a single one.
3660+
status: hasFix
36633661
WarningCode.MUST_BE_IMMUTABLE:
36643662
status: noFix
36653663
WarningCode.MUST_CALL_SUPER:

pkg/analysis_server/lib/src/services/correction/fix.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,6 @@ abstract final class DartFixKind {
10201020
DartFixKindPriority.standard,
10211021
'Make final',
10221022
);
1023-
10241023
// TODO(pq): consider parameterizing: 'Make {fields} final...'
10251024
static const MAKE_FINAL_MULTI = FixKind(
10261025
'dart.fix.makeFinal.multi',
@@ -1072,6 +1071,38 @@ abstract final class DartFixKind {
10721071
DartFixKindPriority.standard,
10731072
'Match an empty map',
10741073
);
1074+
1075+
/// Used when the user has at least one `show` combinator to suggest merging
1076+
/// using `show`.
1077+
static const MERGE_COMBINATORS_SHOW_SHOW = FixKind(
1078+
'dart.fix.mergeCombinatorsShow.show',
1079+
DartFixKindPriority.standard + 1,
1080+
"Merge combinators into a single 'show'",
1081+
);
1082+
1083+
/// Used when the user has only `hide` combinators to suggest merging using
1084+
/// `show`.
1085+
static const MERGE_COMBINATORS_SHOW_HIDE = FixKind(
1086+
'dart.fix.mergeCombinatorsShow.hide',
1087+
DartFixKindPriority.standard,
1088+
"Merge combinators into a single 'show'",
1089+
);
1090+
1091+
/// Used when the user has only `hide` combinators to suggest merging using
1092+
/// `hide`.
1093+
static const MERGE_COMBINATORS_HIDE_HIDE = FixKind(
1094+
'dart.fix.mergeCombinatorsHide.hide',
1095+
DartFixKindPriority.standard + 1,
1096+
"Merge combinators into a single 'hide'",
1097+
);
1098+
1099+
/// Used when the user has at least one `show` combinator to suggest merging
1100+
/// using `hide`.
1101+
static const MERGE_COMBINATORS_HIDE_SHOW = FixKind(
1102+
'dart.fix.mergeCombinatorsHide.show',
1103+
DartFixKindPriority.standard,
1104+
"Merge combinators into a single 'hide'",
1105+
);
10751106
static const MOVE_ANNOTATION_TO_LIBRARY_DIRECTIVE = FixKind(
10761107
'dart.fix.moveAnnotationToLibraryDirective',
10771108
DartFixKindPriority.standard,

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import 'package:analysis_server/src/services/correction/dart/make_return_type_nu
122122
import 'package:analysis_server/src/services/correction/dart/make_super_invocation_last.dart';
123123
import 'package:analysis_server/src/services/correction/dart/make_variable_not_final.dart';
124124
import 'package:analysis_server/src/services/correction/dart/make_variable_nullable.dart';
125+
import 'package:analysis_server/src/services/correction/dart/merge_combinators.dart';
125126
import 'package:analysis_server/src/services/correction/dart/move_annotation_to_library_directive.dart';
126127
import 'package:analysis_server/src/services/correction/dart/move_doc_comment_to_library_directive.dart';
127128
import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart';
@@ -1432,6 +1433,7 @@ final _builtInNonLintMultiGenerators = {
14321433
HintCode.DEPRECATED_MEMBER_USE: [DataDriven.new],
14331434
HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE: [DataDriven.new],
14341435
WarningCode.DEPRECATED_EXPORT_USE: [DataDriven.new],
1436+
WarningCode.MULTIPLE_COMBINATORS: [MergeCombinators.new],
14351437
WarningCode.OVERRIDE_ON_NON_OVERRIDING_METHOD: [DataDriven.new],
14361438
};
14371439

pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import '../../../../utils/test_instrumentation_service.dart';
2525

2626
export 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
2727

28+
typedef ErrorFilter = bool Function(AnalysisError error);
29+
2830
abstract class BaseFixProcessorTest extends AbstractSingleUnitTest {
2931
/// The source change associated with the fix that was found.
3032
late SourceChange change;
@@ -41,9 +43,7 @@ abstract class BaseFixProcessorTest extends AbstractSingleUnitTest {
4143
/// using the [errorFilter] to filter out errors that should be ignored, and
4244
/// expecting that there is a single remaining error. The error filter should
4345
/// return `true` if the error should not be ignored.
44-
Future<AnalysisError> _findErrorToFix({
45-
bool Function(AnalysisError)? errorFilter,
46-
}) async {
46+
Future<AnalysisError> _findErrorToFix({ErrorFilter? errorFilter}) async {
4747
var errors = testAnalysisResult.errors;
4848
if (errorFilter != null) {
4949
if (errors.length == 1) {
@@ -217,9 +217,7 @@ abstract class FixInFileProcessorTest extends BaseFixProcessorTest {
217217
expect(resultCode, expected);
218218
}
219219

220-
Future<List<Fix>> getFixesForFirst(
221-
bool Function(AnalysisError error) test,
222-
) async {
220+
Future<List<Fix>> getFixesForFirst(ErrorFilter test) async {
223221
var errors = testAnalysisResult.errors.where(test);
224222
expect(errors, isNotEmpty);
225223
String? errorCode;
@@ -275,6 +273,17 @@ abstract class FixInFileProcessorTest extends BaseFixProcessorTest {
275273
}
276274
}
277275

276+
/// A base class defining support for writing fix processor tests that are
277+
/// specific to fixes associated with the [errorCode] that use the FixKind.
278+
abstract class FixProcessorErrorCodeTest extends FixProcessorTest {
279+
/// Return the error code being tested.
280+
ErrorCode get errorCode;
281+
282+
ErrorFilter get errorCodeFilter => (e) {
283+
return e.errorCode == errorCode;
284+
};
285+
}
286+
278287
/// A base class defining support for writing fix processor tests that are
279288
/// specific to fixes associated with lints that use the FixKind.
280289
abstract class FixProcessorLintTest extends FixProcessorTest {
@@ -296,7 +305,7 @@ abstract class FixProcessorLintTest extends FixProcessorTest {
296305
return lintCodeSet.single;
297306
}
298307

299-
bool Function(AnalysisError) lintNameFilter(String name) {
308+
ErrorFilter lintNameFilter(String name) {
300309
return (e) {
301310
return e.errorCode is LintCode && e.errorCode.name == name;
302311
};
@@ -318,7 +327,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
318327
/// [expected] output.
319328
Future<void> assertHasFix(
320329
String expected, {
321-
bool Function(AnalysisError error)? errorFilter,
330+
ErrorFilter? errorFilter,
322331
String? target,
323332
int? expectedNumberOfFixesForKind,
324333
String? matchFixMessage,
@@ -376,7 +385,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
376385
/// [expectedNumberOfFixesForKind] fixes of the appropriate kind are found,
377386
/// and that they have messages equal to [matchFixMessages].
378387
Future<void> assertHasFixesWithoutApplying({
379-
bool Function(AnalysisError)? errorFilter,
388+
ErrorFilter? errorFilter,
380389
required int expectedNumberOfFixesForKind,
381390
required List<String> matchFixMessages,
382391
}) async {
@@ -388,9 +397,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
388397
);
389398
}
390399

391-
Future<void> assertHasFixWithoutApplying({
392-
bool Function(AnalysisError)? errorFilter,
393-
}) async {
400+
Future<void> assertHasFixWithoutApplying({ErrorFilter? errorFilter}) async {
394401
var error = await _findErrorToFix(errorFilter: errorFilter);
395402
var fix = await _assertHasFix(error);
396403
change = fix.change;
@@ -419,7 +426,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
419426

420427
/// Compute fixes and ensure that there is no fix of the [kind] being tested by
421428
/// this class.
422-
Future<void> assertNoFix({bool Function(AnalysisError)? errorFilter}) async {
429+
Future<void> assertNoFix({ErrorFilter? errorFilter}) async {
423430
var error = await _findErrorToFix(errorFilter: errorFilter);
424431
await _assertNoFix(error);
425432
}

0 commit comments

Comments
 (0)