Skip to content

Commit 6eda5e2

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Fixes add const for final variables and lint
[email protected] Fixes #49294 Change-Id: I75dd2c0063ea5d3a6ebbf4afb734d2cb5d98c1d2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392860 Reviewed-by: Phil Quitslund <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Auto-Submit: Felipe Morschel <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Commit-Queue: Phil Quitslund <[email protected]>
1 parent 11783f1 commit 6eda5e2

File tree

4 files changed

+185
-4
lines changed

4 files changed

+185
-4
lines changed

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

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analysis_server/src/services/correction/fix.dart';
6+
import 'package:analysis_server/src/utilities/extensions/ast.dart';
67
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
78
import 'package:analyzer/dart/ast/ast.dart';
89
import 'package:analyzer/dart/ast/token.dart';
@@ -13,6 +14,7 @@ import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart
1314
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
1415
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
1516
import 'package:analyzer_plugin/utilities/range_factory.dart';
17+
import 'package:linter/src/lint_codes.dart';
1618

1719
class AddConst extends ResolvedCorrectionProducer {
1820
AddConst({required super.context});
@@ -97,9 +99,12 @@ class AddConst extends ResolvedCorrectionProducer {
9799
DartFileEditBuilderImpl builder, Expression targetNode) {
98100
var edits = builder.fileEdit.edits;
99101
var child = targetNode.parent;
100-
while (child is Expression || child is ArgumentList) {
102+
while (child is Expression ||
103+
child is ArgumentList ||
104+
child is VariableDeclaration ||
105+
child is VariableDeclarationList) {
101106
if (edits.any((element) =>
102-
element.replacement == 'const ' &&
107+
element.replacement.startsWith('const') &&
103108
element.offset == child!.offset)) {
104109
return true;
105110
}
@@ -137,13 +142,77 @@ class AddConst extends ResolvedCorrectionProducer {
137142
if (targetNode is ConstructorName) {
138143
targetNode = targetNode.parent;
139144
}
140-
if (targetNode is InstanceCreationExpression) {
141-
if (targetNode.keyword == null) {
145+
if (targetNode case InstanceCreationExpression(:var parent, :var keyword)) {
146+
var constDeclarations =
147+
getCodeStyleOptions(unitResult.file).preferConstDeclarations;
148+
149+
if (parent is VariableDeclaration && constDeclarations) {
150+
if (parent.parent
151+
case VariableDeclarationList(:var finalKeyword?, :var variables)
152+
when _declarationListIsFullyConst(variables)) {
153+
await builder.addDartFileEdit(file, (builder) {
154+
builder.addSimpleReplacement(range.token(finalKeyword), 'const');
155+
});
156+
return;
157+
}
158+
}
159+
if (keyword == null) {
142160
await insertAtOffset(targetNode);
143161
return;
144162
}
145163
}
146164
}
165+
166+
/// Considers if the given [variables] to be declared with `const` if all of
167+
/// them are contained in the list of errors that suggest using `const` (
168+
/// `prefer_const_constructors` triggers).
169+
///
170+
/// This is used to determine if a `const` keyword should be added to a
171+
/// variable declaration list. E.g.:
172+
///
173+
/// ```dart
174+
/// final a = 1, b = 2;
175+
/// ```
176+
///
177+
/// Would then be transformed to:
178+
///
179+
/// ```dart
180+
/// const a = 1, b = 2;
181+
/// ```
182+
///
183+
/// If not all of the variables are contained in the list of errors that
184+
/// suggest using `const` then the `const` keyword should not be added. E.g.:
185+
///
186+
/// ```dart
187+
/// class C {
188+
/// const C();
189+
/// }
190+
/// final c = C(), d = Future.value(1);
191+
/// ```
192+
///
193+
/// Would be transformed to:
194+
///
195+
/// ```dart
196+
/// final c = const C(), d = Future.value(1);
197+
/// ```
198+
///
199+
/// If other diagnostics are to be fixed with this CorrectionProducer the
200+
/// inner test for `prefer_const_constructors` will need to be amended.
201+
bool _declarationListIsFullyConst(NodeList<VariableDeclaration> variables) {
202+
var errors = [
203+
...unitResult.errors.where(
204+
(error) => error.errorCode == LinterLintCode.prefer_const_constructors,
205+
),
206+
];
207+
var errorsRanges = errors.map(range.error);
208+
var variablesRanges = variables.map((v) {
209+
var initializer = v.initializer;
210+
if (initializer == null) return range.node(v);
211+
return range.node(initializer);
212+
});
213+
// If each of the variable ranges is contained in the list of error ranges.
214+
return variablesRanges.every(errorsRanges.contains);
215+
}
147216
}
148217

149218
class _ConstRangeFinder extends RecursiveAstVisitor<void> {

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,86 @@ class AddConst_PreferConstConstructorsBulkTest extends BulkFixProcessorTest {
425425
@override
426426
String get lintCode => LintNames.prefer_const_constructors;
427427

428+
Future<void> test_final() async {
429+
writeTestPackageConfig(meta: true);
430+
createAnalysisOptionsFile(lints: [
431+
LintNames.prefer_const_constructors,
432+
LintNames.prefer_const_declarations,
433+
]);
434+
await resolveTestCode(r'''
435+
class C {
436+
const C([C? c]);
437+
}
438+
final c = C(C());
439+
''');
440+
await assertHasFix(r'''
441+
class C {
442+
const C([C? c]);
443+
}
444+
const c = C(C());
445+
''');
446+
}
447+
448+
Future<void> test_final_variableDeclarationList_const() async {
449+
writeTestPackageConfig(meta: true);
450+
createAnalysisOptionsFile(lints: [
451+
LintNames.prefer_const_constructors,
452+
LintNames.prefer_const_declarations,
453+
]);
454+
await resolveTestCode(r'''
455+
class C {
456+
const C([C? c]);
457+
}
458+
final c1 = C(C()), c2 = C();
459+
''');
460+
await assertHasFix(r'''
461+
class C {
462+
const C([C? c]);
463+
}
464+
const c1 = C(C()), c2 = C();
465+
''');
466+
}
467+
468+
Future<void> test_final_variableDeclarationList_nonConst_first() async {
469+
writeTestPackageConfig(meta: true);
470+
createAnalysisOptionsFile(lints: [
471+
LintNames.prefer_const_constructors,
472+
LintNames.prefer_const_declarations,
473+
]);
474+
await resolveTestCode(r'''
475+
class C {
476+
const C([C? c]);
477+
}
478+
final f = Future.value(7), c = C(C());
479+
''');
480+
await assertHasFix(r'''
481+
class C {
482+
const C([C? c]);
483+
}
484+
final f = Future.value(7), c = const C(C());
485+
''');
486+
}
487+
488+
Future<void> test_final_variableDeclarationList_nonConst_last() async {
489+
writeTestPackageConfig(meta: true);
490+
createAnalysisOptionsFile(lints: [
491+
LintNames.prefer_const_constructors,
492+
LintNames.prefer_const_declarations,
493+
]);
494+
await resolveTestCode(r'''
495+
class C {
496+
const C([C? c]);
497+
}
498+
final c = C(C()), f = Future.value(7);
499+
''');
500+
await assertHasFix(r'''
501+
class C {
502+
const C([C? c]);
503+
}
504+
final c = const C(C()), f = Future.value(7);
505+
''');
506+
}
507+
428508
Future<void> test_noKeyword() async {
429509
writeTestPackageConfig(meta: true);
430510
await resolveTestCode(r'''
@@ -539,6 +619,31 @@ class AddConst_PreferConstConstructorsTest extends FixProcessorLintTest {
539619
@override
540620
String get lintCode => LintNames.prefer_const_constructors;
541621

622+
Future<void> test_final_variable() async {
623+
createAnalysisOptionsFile(lints: [
624+
LintNames.prefer_const_constructors,
625+
LintNames.prefer_const_declarations,
626+
]);
627+
await resolveTestCode('''
628+
class C {
629+
const C();
630+
}
631+
void f() {
632+
final c = C();
633+
print(c);
634+
}
635+
''');
636+
await assertHasFix('''
637+
class C {
638+
const C();
639+
}
640+
void f() {
641+
const c = C();
642+
print(c);
643+
}
644+
''');
645+
}
646+
542647
Future<void> test_new() async {
543648
await resolveTestCode('''
544649
class C {

pkg/analyzer/lib/dart/analysis/code_style_options.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ abstract class CodeStyleOptions {
1919
/// Whether local variables should be `final` whenever possible.
2020
bool get makeLocalsFinal;
2121

22+
/// Whether `prefer_const_declarations` is enabled.
23+
bool get preferConstDeclarations;
24+
2225
/// Whether `prefer_int_literals` is enabled.
2326
bool get preferIntLiterals;
2427

pkg/analyzer/lib/src/analysis_options/code_style_options.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ class CodeStyleOptionsImpl implements CodeStyleOptions {
2525
@override
2626
bool get makeLocalsFinal => _isLintEnabled('prefer_final_locals');
2727

28+
@override
29+
bool get preferConstDeclarations =>
30+
_isLintEnabled('prefer_const_declarations');
31+
2832
@override
2933
bool get preferIntLiterals => _isLintEnabled('prefer_int_literals');
3034

0 commit comments

Comments
 (0)