Skip to content

Commit 92663b7

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Adds fix for expected 'on' keyword
[email protected] Fixes #47127 Change-Id: I316a2d29546c9edd3a808785efdbc148007c531b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392540 Auto-Submit: Felipe Morschel <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
1 parent 6867428 commit 92663b7

File tree

6 files changed

+201
-1
lines changed

6 files changed

+201
-1
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2024, 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/dart/ast/ast.dart';
8+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
9+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
10+
11+
class InsertOnKeyword extends ResolvedCorrectionProducer {
12+
InsertOnKeyword({required super.context});
13+
14+
@override
15+
CorrectionApplicability get applicability =>
16+
// Supports single instance and in file corrections
17+
CorrectionApplicability
18+
.acrossSingleFile;
19+
20+
@override
21+
FixKind get fixKind => DartFixKind.INSERT_ON_KEYWORD;
22+
23+
@override
24+
FixKind get multiFixKind => DartFixKind.INSERT_ON_KEYWORD_MULTI;
25+
26+
@override
27+
Future<void> compute(ChangeBuilder builder) async {
28+
var node = this.node;
29+
if (node is! ExtensionDeclaration) {
30+
if (node.parent case ExtensionDeclaration parent) {
31+
node = parent;
32+
} else {
33+
return;
34+
}
35+
}
36+
37+
var onClause = node.onClause;
38+
if (onClause != null && onClause.onKeyword.isSynthetic) {
39+
var onOffset = onClause.onKeyword.offset;
40+
if (onClause.extendedType.length == 0) {
41+
onOffset = node.name?.offset ?? onOffset;
42+
}
43+
44+
await builder.addDartFileEdit(file, (builder) {
45+
builder.addSimpleInsertion(onOffset, 'on ');
46+
});
47+
}
48+
}
49+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,16 @@ abstract final class DartFixKind {
899899
DartFixKindPriority.standard,
900900
'Insert body',
901901
);
902+
static const INSERT_ON_KEYWORD = FixKind(
903+
'dart.fix.insertOnKeyword',
904+
DartFixKindPriority.standard,
905+
"Insert 'on' keyword",
906+
);
907+
static const INSERT_ON_KEYWORD_MULTI = FixKind(
908+
'dart.fix.insertOnKeyword.multi',
909+
DartFixKindPriority.inFile,
910+
"Insert 'on' keyword in file",
911+
);
902912
static const INSERT_SEMICOLON = FixKind(
903913
'dart.fix.insertSemicolon',
904914
DartFixKindPriority.standard,

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ import 'package:analysis_server/src/services/correction/dart/import_library.dart
105105
import 'package:analysis_server/src/services/correction/dart/inline_invocation.dart';
106106
import 'package:analysis_server/src/services/correction/dart/inline_typedef.dart';
107107
import 'package:analysis_server/src/services/correction/dart/insert_body.dart';
108+
import 'package:analysis_server/src/services/correction/dart/insert_on_keyword.dart';
108109
import 'package:analysis_server/src/services/correction/dart/insert_semicolon.dart';
109110
import 'package:analysis_server/src/services/correction/dart/make_class_abstract.dart';
110111
import 'package:analysis_server/src/services/correction/dart/make_conditional_on_debug_mode.dart';
@@ -1134,7 +1135,11 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
11341135
ParserErrorCode.EXPECTED_SWITCH_EXPRESSION_BODY: [InsertBody.new],
11351136
ParserErrorCode.EXPECTED_SWITCH_STATEMENT_BODY: [InsertBody.new],
11361137
ParserErrorCode.EXPECTED_TRY_STATEMENT_BODY: [InsertBody.new],
1137-
ParserErrorCode.EXPECTED_TOKEN: [InsertSemicolon.new, ReplaceWithArrow.new],
1138+
ParserErrorCode.EXPECTED_TOKEN: [
1139+
InsertSemicolon.new,
1140+
ReplaceWithArrow.new,
1141+
InsertOnKeyword.new,
1142+
],
11381143
ParserErrorCode.EXTENSION_AUGMENTATION_HAS_ON_CLAUSE: [RemoveOnClause.new],
11391144
ParserErrorCode.EXTENSION_DECLARES_CONSTRUCTOR: [RemoveConstructor.new],
11401145
ParserErrorCode.EXTERNAL_CLASS: [RemoveLexeme.modifier],

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,23 @@ abstract class FixInFileProcessorTest extends BaseFixProcessorTest {
217217
expect(resultCode, expected);
218218
}
219219

220+
Future<List<Fix>> getFixesForFirst(
221+
bool Function(AnalysisError error) test,
222+
) async {
223+
var errors = testAnalysisResult.errors.where(test);
224+
expect(errors, isNotEmpty);
225+
String? errorCode;
226+
for (var error in errors) {
227+
errorCode ??= error.errorCode.name;
228+
if (errorCode != error.errorCode.name) {
229+
fail('Expected only errors of one type but found: $errors');
230+
}
231+
}
232+
233+
var fixes = await _computeFixes(errors.first);
234+
return fixes;
235+
}
236+
220237
Future<List<Fix>> getFixesForFirstError() async {
221238
var errors = testAnalysisResult.errors;
222239
expect(errors, isNotEmpty);
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) 2024, 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/src/dart/error/syntactic_errors.dart';
7+
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
8+
import 'package:test/expect.dart';
9+
import 'package:test_reflective_loader/test_reflective_loader.dart';
10+
11+
import 'fix_processor.dart';
12+
13+
void main() {
14+
defineReflectiveSuite(() {
15+
defineReflectiveTests(InsertOnKeywordMultiTest);
16+
defineReflectiveTests(InsertOnKeywordTest);
17+
});
18+
}
19+
20+
@reflectiveTest
21+
class InsertOnKeywordMultiTest extends FixInFileProcessorTest {
22+
Future<void> test_expected_onKeyword_multi() async {
23+
await resolveTestCode('''
24+
extension String {}
25+
extension String {}
26+
''');
27+
var fixes = await getFixesForFirst(
28+
(e) => e.errorCode == ParserErrorCode.EXPECTED_TOKEN,
29+
);
30+
expect(fixes, hasLength(1));
31+
assertProduces(fixes.first, r'''
32+
extension on String {}
33+
extension on String {}
34+
''');
35+
}
36+
}
37+
38+
@reflectiveTest
39+
class InsertOnKeywordTest extends FixProcessorTest {
40+
@override
41+
FixKind get kind => DartFixKind.INSERT_ON_KEYWORD;
42+
43+
Future<void> test_expected_onKeyword() async {
44+
await resolveTestCode('''
45+
extension int {}
46+
''');
47+
await assertHasFix(
48+
'''
49+
extension on int {}
50+
''',
51+
errorFilter: (error) {
52+
return error.errorCode == ParserErrorCode.EXPECTED_TOKEN;
53+
},
54+
);
55+
}
56+
57+
Future<void> test_expected_onKeyword_betweenNameAndType() async {
58+
await resolveTestCode('''
59+
extension E int {}
60+
''');
61+
await assertHasFix('''
62+
extension E on int {}
63+
''');
64+
}
65+
66+
Future<void> test_expected_onKeyword_betweenTypeParameterAndType() async {
67+
await resolveTestCode('''
68+
extension E<T> int {}
69+
''');
70+
await assertHasFix('''
71+
extension E<T> on int {}
72+
''');
73+
}
74+
75+
Future<void> test_expected_onKeyword_nonType() async {
76+
await resolveTestCode('''
77+
extension UnresolvedType {}
78+
''');
79+
await assertHasFix(
80+
'''
81+
extension on UnresolvedType {}
82+
''',
83+
errorFilter: (error) {
84+
return error.errorCode == ParserErrorCode.EXPECTED_TOKEN;
85+
},
86+
);
87+
}
88+
89+
Future<void> test_expected_onKeyword_nonTypeWithTypeArguments() async {
90+
// We want to believe that the type parameter is from the undefined type.
91+
await resolveTestCode('''
92+
extension UnresolvedType<T> {}
93+
''');
94+
await assertHasFix(
95+
'''
96+
extension on UnresolvedType<T> {}
97+
''',
98+
errorFilter: (error) {
99+
return error.errorCode == ParserErrorCode.EXPECTED_TOKEN;
100+
},
101+
);
102+
}
103+
104+
Future<void> test_expected_onKeyword_typeWithTypeArguments() async {
105+
await resolveTestCode('''
106+
extension List<int> {}
107+
''');
108+
await assertHasFix(
109+
'''
110+
extension on List<int> {}
111+
''',
112+
errorFilter: (error) {
113+
return error.errorCode == ParserErrorCode.EXPECTED_TOKEN;
114+
},
115+
);
116+
}
117+
}

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
@@ -140,6 +140,7 @@ import 'import_library_show_test.dart' as import_library_show;
140140
import 'inline_invocation_test.dart' as inline_invocation;
141141
import 'inline_typedef_test.dart' as inline_typedef;
142142
import 'insert_body_test.dart' as insert_body;
143+
import 'insert_on_keyword_test.dart' as insert_on_keyword;
143144
import 'insert_semicolon_test.dart' as insert_semicolon;
144145
import 'make_class_abstract_test.dart' as make_class_abstract;
145146
import 'make_conditional_on_debug_mode_test.dart'
@@ -423,6 +424,7 @@ void main() {
423424
inline_invocation.main();
424425
inline_typedef.main();
425426
insert_body.main();
427+
insert_on_keyword.main();
426428
insert_semicolon.main();
427429
make_class_abstract.main();
428430
make_conditional_on_debug_mode.main();

0 commit comments

Comments
 (0)