Skip to content

Commit fa2f2dd

Browse files
pqCommit Queue
authored andcommitted
[fix] add remove_ignore correction producer
Change-Id: I48ed5c612492e79e1b42ffd8e1522e960981fe63 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404524 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Phil Quitslund <[email protected]>
1 parent b115ec0 commit fa2f2dd

File tree

7 files changed

+219
-12
lines changed

7 files changed

+219
-12
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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_plugin/edit/dart/correction_producer.dart';
7+
import 'package:analyzer/source/source_range.dart';
8+
import 'package:analyzer/src/dart/ast/token.dart';
9+
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
10+
import 'package:analyzer/src/utilities/extensions/ast.dart';
11+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
12+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
13+
14+
class RemoveIgnore extends ResolvedCorrectionProducer {
15+
String _diagnosticName = '';
16+
RemoveIgnore({required super.context});
17+
18+
@override
19+
CorrectionApplicability get applicability =>
20+
CorrectionApplicability.singleLocation;
21+
22+
@override
23+
List<String> get fixArguments => [_diagnosticName];
24+
25+
@override
26+
FixKind get fixKind => DartFixKind.REMOVE_IGNORED_DIAGNOSTIC;
27+
28+
@override
29+
Future<void> compute(ChangeBuilder builder) async {
30+
var diagnostic = this.diagnostic;
31+
if (diagnostic == null) return;
32+
33+
var diagnosticOffset = diagnostic.problemMessage.offset;
34+
35+
var comment = node.commentTokenCovering(diagnosticOffset);
36+
if (comment is! CommentToken) return;
37+
38+
SourceRange? rangeToDelete;
39+
40+
var ignoredElements = comment.ignoredElements.toList();
41+
42+
for (var (index, ignoredElement) in ignoredElements.indexed) {
43+
if (ignoredElement is! IgnoredDiagnosticName) continue;
44+
45+
var ignoredElementOffset = ignoredElement.offset;
46+
if (ignoredElement.offset == diagnosticOffset) {
47+
_diagnosticName = ignoredElement.name;
48+
var scanBack = index != 0;
49+
var commentText = comment.lexeme;
50+
if (scanBack) {
51+
// Scan back for a preceding comma:
52+
for (var offset = ignoredElementOffset; offset > -1; --offset) {
53+
if (commentText[offset] == ',') {
54+
var backSteps = ignoredElementOffset - offset;
55+
rangeToDelete = SourceRange(
56+
diagnosticOffset - backSteps,
57+
_diagnosticName.length + backSteps,
58+
);
59+
break;
60+
}
61+
}
62+
} else {
63+
// Scan forward for a trailing comma:
64+
var chars = commentText.substring(ignoredElementOffset).indexOf(',');
65+
if (chars == -1) return;
66+
67+
// Eat the comma
68+
chars++;
69+
70+
// Eat a trailing space if needed
71+
if (commentText[ignoredElementOffset + chars] == ' ') chars++;
72+
73+
rangeToDelete = SourceRange(ignoredElementOffset, chars);
74+
}
75+
}
76+
}
77+
78+
if (rangeToDelete != null) {
79+
await builder.addDartFileEdit(file, (builder) {
80+
builder.addDeletion(rangeToDelete!);
81+
});
82+
}
83+
}
84+
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2424,13 +2424,15 @@ LintCode.unnecessary_getters_setters:
24242424
LintCode.unnecessary_ignore:
24252425
status: needsFix
24262426
notes: |-
2427-
Remove the ignore comment (or one code in the comment).
2427+
Remove the ignore comment.
24282428
LintCode.unnecessary_ignore_file:
24292429
status: needsFix
2430+
notes: |-
2431+
Remove the ignore comment.
24302432
LintCode.unnecessary_ignore_name:
2431-
status: needsFix
2433+
status: hasFix
24322434
LintCode.unnecessary_ignore_name_file:
2433-
status: needsFix
2435+
status: hasFix
24342436
LintCode.unnecessary_lambdas:
24352437
status: hasFix
24362438
LintCode.unnecessary_late:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,11 @@ abstract final class DartFixKind {
12661266
DartFixKindPriority.inFile,
12671267
"Remove unnecessary '??' operators everywhere in file",
12681268
);
1269+
static const REMOVE_IGNORED_DIAGNOSTIC = FixKind(
1270+
'dart.fix.remove.ignored.diagnostic',
1271+
DartFixKindPriority.standard,
1272+
'Remove {0}',
1273+
);
12691274
static const REMOVE_INVOCATION = FixKind(
12701275
'dart.fix.remove.invocation',
12711276
DartFixKindPriority.standard,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import 'package:analysis_server/src/services/correction/dart/remove_empty_else.d
146146
import 'package:analysis_server/src/services/correction/dart/remove_empty_statement.dart';
147147
import 'package:analysis_server/src/services/correction/dart/remove_extends_clause.dart';
148148
import 'package:analysis_server/src/services/correction/dart/remove_if_null_operator.dart';
149+
import 'package:analysis_server/src/services/correction/dart/remove_ignore.dart';
149150
import 'package:analysis_server/src/services/correction/dart/remove_initializer.dart';
150151
import 'package:analysis_server/src/services/correction/dart/remove_interpolation_braces.dart';
151152
import 'package:analysis_server/src/services/correction/dart/remove_invocation.dart';
@@ -485,6 +486,11 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
485486
LinterLintCode.unnecessary_final_with_type: [ReplaceFinalWithVar.new],
486487
LinterLintCode.unnecessary_final_without_type: [ReplaceFinalWithVar.new],
487488
LinterLintCode.unnecessary_getters_setters: [MakeFieldPublic.new],
489+
LinterLintCode.unnecessary_ignore_name: [RemoveIgnore.new],
490+
LinterLintCode.unnecessary_ignore_name_file: [RemoveIgnore.new],
491+
// TODO(pq): add =>
492+
// LinterLintCode.unnecessary_ignore: [RemoveComment.new],
493+
// LinterLintCode.unnecessary_ignore_file: [RemoveComment.new],
488494
LinterLintCode.unnecessary_lambdas: [ReplaceWithTearOff.new],
489495
LinterLintCode.unnecessary_late: [RemoveUnnecessaryLate.new],
490496
LinterLintCode.unnecessary_library_directive: [
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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:analyzer_plugin/utilities/fixes/fixes.dart';
7+
import 'package:linter/src/lint_names.dart';
8+
import 'package:test_reflective_loader/test_reflective_loader.dart';
9+
10+
import 'fix_processor.dart';
11+
12+
void main() {
13+
defineReflectiveSuite(() {
14+
defineReflectiveTests(RemoveUnnecessaryIgnoreTest);
15+
});
16+
}
17+
18+
// TODO(pq): add bulk fix tests
19+
@reflectiveTest
20+
class RemoveUnnecessaryIgnoreTest extends FixProcessorLintTest {
21+
@override
22+
FixKind get kind => DartFixKind.REMOVE_IGNORED_DIAGNOSTIC;
23+
24+
@override
25+
String get lintCode => LintNames.unnecessary_ignore;
26+
27+
Future<void> test_file_first() async {
28+
await resolveTestCode('''
29+
// ignore_for_file: unused_local_variable, return_of_invalid_type
30+
int f() => null;
31+
''');
32+
await assertHasFix('''
33+
// ignore_for_file: return_of_invalid_type
34+
int f() => null;
35+
''');
36+
}
37+
38+
Future<void> test_file_last() async {
39+
await resolveTestCode('''
40+
// ignore_for_file: return_of_invalid_type, unused_local_variable
41+
int f() => null;
42+
''');
43+
await assertHasFix('''
44+
// ignore_for_file: return_of_invalid_type
45+
int f() => null;
46+
''');
47+
}
48+
49+
Future<void> test_file_middle() async {
50+
await resolveTestCode('''
51+
// ignore_for_file: return_of_invalid_type, unused_local_variable, non_bool_negation_expression
52+
int f() => !null;
53+
''');
54+
await assertHasFix('''
55+
// ignore_for_file: return_of_invalid_type, non_bool_negation_expression
56+
int f() => !null;
57+
''');
58+
}
59+
60+
Future<void> test_line_first() async {
61+
await resolveTestCode('''
62+
// ignore: unused_local_variable, return_of_invalid_type
63+
int f() => null;
64+
''');
65+
await assertHasFix('''
66+
// ignore: return_of_invalid_type
67+
int f() => null;
68+
''');
69+
}
70+
71+
Future<void> test_line_last() async {
72+
await resolveTestCode('''
73+
// ignore: return_of_invalid_type, unused_local_variable
74+
int f() => null;
75+
''');
76+
await assertHasFix('''
77+
// ignore: return_of_invalid_type
78+
int f() => null;
79+
''');
80+
}
81+
82+
Future<void> test_line_middle() async {
83+
await resolveTestCode('''
84+
// ignore: return_of_invalid_type, unused_local_variable, non_bool_negation_expression
85+
int f() => !null;
86+
''');
87+
await assertHasFix('''
88+
// ignore: return_of_invalid_type, non_bool_negation_expression
89+
int f() => !null;
90+
''');
91+
}
92+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ import 'remove_empty_else_test.dart' as remove_empty_else;
187187
import 'remove_empty_statement_test.dart' as remove_empty_statement;
188188
import 'remove_extends_clause_test.dart' as remove_extends_clause;
189189
import 'remove_if_null_operator_test.dart' as remove_if_null_operator;
190+
import 'remove_ignore_test.dart' as remove_ignore;
190191
import 'remove_initializer_test.dart' as remove_initializer;
191192
import 'remove_interpolation_braces_test.dart' as remove_interpolation_braces;
192193
import 'remove_invocation_test.dart' as remove_invocation;
@@ -465,6 +466,7 @@ void main() {
465466
remove_empty_statement.main();
466467
remove_extends_clause.main();
467468
remove_if_null_operator.main();
469+
remove_ignore.main();
468470
remove_initializer.main();
469471
remove_interpolation_braces.main();
470472
remove_invocation.main();

pkg/analyzer/lib/src/error/ignore_validator.dart

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,21 @@ class IgnoreValidator {
107107
_reportUnignorableAndDuplicateIgnores(
108108
unignorable, duplicated, ignoredOnLine);
109109
}
110+
111+
// If there's more than one ignore, the fix is to remove the name.
112+
// Otherwise, the entire comment can be removed.
113+
var ignoredForFileCount = ignoredForFile.length;
114+
var ignoredOnLineCount = 0;
115+
110116
//
111117
// Remove all of the errors that are actually being ignored.
112118
//
113119
for (var error in _reportedErrors) {
114120
var lineNumber = _lineInfo.getLocation(error.offset).lineNumber;
115121
var ignoredOnLine = ignoredOnLineMap[lineNumber];
122+
if (ignoredOnLine != null) {
123+
ignoredOnLineCount += ignoredOnLine.length;
124+
}
116125

117126
ignoredForFile.removeByName(error.ignoreName);
118127
ignoredForFile.removeByName(error.ignoreUniqueName);
@@ -123,10 +132,15 @@ class IgnoreValidator {
123132
//
124133
// Report any remaining ignored names as being unnecessary.
125134
//
126-
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(ignoredForFile,
127-
forFile: true);
135+
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(
136+
ignoredForFile,
137+
ignoredForFileCount: ignoredForFileCount,
138+
);
128139
for (var ignoredOnLine in ignoredOnLineMap.values) {
129-
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(ignoredOnLine);
140+
_reportUnnecessaryOrRemovedOrDeprecatedIgnores(
141+
ignoredOnLine,
142+
ignoredOnLineCount: ignoredOnLineCount,
143+
);
130144
}
131145
}
132146

@@ -172,7 +186,8 @@ class IgnoreValidator {
172186
/// Report the [ignoredNames] as being unnecessary.
173187
void _reportUnnecessaryOrRemovedOrDeprecatedIgnores(
174188
List<IgnoredElement> ignoredNames,
175-
{bool forFile = false}) {
189+
{int? ignoredForFileCount,
190+
int? ignoredOnLineCount}) {
176191
if (!_validateUnnecessaryIgnores) return;
177192

178193
for (var ignoredName in ignoredNames) {
@@ -210,13 +225,14 @@ class IgnoreValidator {
210225
}
211226

212227
late ErrorCode lintCode;
213-
if (ignoredNames.length > 1) {
214-
lintCode = forFile
228+
229+
if (ignoredForFileCount != null) {
230+
lintCode = ignoredForFileCount > 1
215231
? unnecessaryIgnoreNameFileLintCode
216-
: unnecessaryIgnoreNameLocationLintCode;
232+
: unnecessaryIgnoreFileLintCode;
217233
} else {
218-
lintCode = forFile
219-
? unnecessaryIgnoreFileLintCode
234+
lintCode = ignoredOnLineCount! > 1
235+
? unnecessaryIgnoreNameLocationLintCode
220236
: unnecessaryIgnoreLocationLintCode;
221237
}
222238

0 commit comments

Comments
 (0)