Skip to content

Commit 6a54fdd

Browse files
srawlinsCommit Bot
authored and
Commit Bot
committed
analyzer: Mark implicit-casts and implicit-dynamic as deprecated
Also add a fix which replaces 'implicit-casts' with 'strict-casts' and a fix which replaces 'implicit-dynamic' with 'strict-raw-types'. Fixes #47902 Change-Id: I293c3c1fd671ac85c12f9c050465b4d77632f241 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/224800 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 92a2a74 commit 6a54fdd

File tree

14 files changed

+588
-67
lines changed

14 files changed

+588
-67
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ class AnalysisOptionsFixKind {
4040
50,
4141
"Remove '{0}'",
4242
);
43+
static const REPLACE_WITH_STRICT_CASTS = FixKind(
44+
'analysisOptions.fix.replaceWithStrictCasts',
45+
50,
46+
'Replace with the strict-casts analysis mode',
47+
);
48+
static const REPLACE_WITH_STRICT_RAW_TYPES = FixKind(
49+
'analysisOptions.fix.replaceWithStrictRawTypes',
50+
50,
51+
'Replace with the strict-raw-types analysis mode',
52+
);
4353
}
4454

4555
/// The implementation of [DartFixContext].

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

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import 'package:analyzer/src/generated/java_core.dart';
1616
import 'package:analyzer/src/generated/source.dart';
1717
import 'package:analyzer/src/lint/options_rule_validator.dart';
1818
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
19+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_yaml.dart';
1920
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
2021
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
22+
import 'package:collection/collection.dart';
2123
import 'package:yaml/yaml.dart';
24+
import 'package:yaml_edit/yaml_edit.dart';
2225

2326
/// The generator used to generate fixes in analysis options files.
2427
class AnalysisOptionsFixGenerator {
@@ -67,7 +70,26 @@ class AnalysisOptionsFixGenerator {
6770
// AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED) {
6871
// } else
6972

70-
if (errorCode == DEPRECATED_LINT_HINT) {
73+
if (errorCode ==
74+
AnalysisOptionsWarningCode
75+
.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT) {
76+
var analyzerMap = options['analyzer'];
77+
if (analyzerMap is! YamlMap) {
78+
return fixes;
79+
}
80+
var strongModeMap = analyzerMap['strong-mode'];
81+
82+
if (strongModeMap is! YamlMap) {
83+
return fixes;
84+
}
85+
if (_isErrorAtMapKey(strongModeMap, 'implicit-casts')) {
86+
await _addFix_replaceWithStrictCasts(
87+
coveringNodePath, analyzerMap, strongModeMap);
88+
} else if (_isErrorAtMapKey(strongModeMap, 'implicit-dynamic')) {
89+
await _addFix_replaceWithStrictRawTypes(
90+
coveringNodePath, analyzerMap, strongModeMap);
91+
}
92+
} else if (errorCode == DEPRECATED_LINT_HINT) {
7193
await _addFix_removeLint(coveringNodePath);
7294
} else if (errorCode ==
7395
AnalysisOptionsHintCode.SUPER_MIXINS_SETTING_DEPRECATED) {
@@ -109,6 +131,52 @@ class AnalysisOptionsFixGenerator {
109131
}
110132
}
111133

134+
/// Replaces `analyzer: strong-mode: implicit-casts: false` with
135+
/// `analyzer: language: strict-casts: true`.
136+
Future<void> _addFix_replaceWithStrictCasts(List<YamlNode> coveringNodePath,
137+
YamlMap analyzerMap, YamlMap strongModeMap) async {
138+
var builder =
139+
ChangeBuilder(workspace: _NonDartChangeWorkspace(resourceProvider));
140+
await builder.addYamlFileEdit(file, (builder) {
141+
_replaceStrongModeEntryWithLanguageEntry(
142+
builder,
143+
coveringNodePath,
144+
analyzerMap,
145+
strongModeMap,
146+
strongModeKey: 'implicit-casts',
147+
languageKey: 'strict-casts',
148+
languageValue: true,
149+
);
150+
});
151+
_addFixFromBuilder(
152+
builder, AnalysisOptionsFixKind.REPLACE_WITH_STRICT_CASTS,
153+
args: [coveringNodePath[0].toString()]);
154+
}
155+
156+
/// Replaces `analyzer: strong-mode: implicit-dynamic: false` with
157+
/// `analyzer: language: strict-raw-types: true`.
158+
Future<void> _addFix_replaceWithStrictRawTypes(
159+
List<YamlNode> coveringNodePath,
160+
YamlMap analyzerMap,
161+
YamlMap strongModeMap) async {
162+
var builder =
163+
ChangeBuilder(workspace: _NonDartChangeWorkspace(resourceProvider));
164+
await builder.addYamlFileEdit(file, (builder) {
165+
_replaceStrongModeEntryWithLanguageEntry(
166+
builder,
167+
coveringNodePath,
168+
analyzerMap,
169+
strongModeMap,
170+
strongModeKey: 'implicit-dynamic',
171+
languageKey: 'strict-raw-types',
172+
languageValue: true,
173+
);
174+
});
175+
_addFixFromBuilder(
176+
builder, AnalysisOptionsFixKind.REPLACE_WITH_STRICT_RAW_TYPES,
177+
args: [coveringNodePath[0].toString()]);
178+
}
179+
112180
/// Add a fix whose edits were built by the [builder] that has the given
113181
/// [kind]. If [args] are provided, they will be used to fill in the message
114182
/// for the fix.
@@ -191,6 +259,20 @@ class AnalysisOptionsFixGenerator {
191259
return offset;
192260
}
193261

262+
/// Returns whether the error is located within [map], covering the
263+
/// [YamlScalar] node for [key].
264+
bool _isErrorAtMapKey(YamlMap map, String key) {
265+
var keyNode = map.nodes.keys
266+
.whereType<YamlScalar>()
267+
.firstWhereOrNull((k) => k.value == key);
268+
if (keyNode == null) {
269+
return false;
270+
}
271+
var keyOffset = keyNode.span.start.offset;
272+
var keyLength = keyNode.span.end.offset - keyOffset;
273+
return keyOffset == errorOffset && keyLength == errorLength;
274+
}
275+
194276
SourceRange _lines(int start, int end) {
195277
var startLocation = lineInfo.getLocation(start);
196278
var startOffset = lineInfo.getOffsetOfLine(startLocation.lineNumber - 1);
@@ -199,6 +281,53 @@ class AnalysisOptionsFixGenerator {
199281
math.min(endLocation.lineNumber, lineInfo.lineCount - 1));
200282
return SourceRange(startOffset, endOffset - startOffset);
201283
}
284+
285+
/// Replaces a 'strong-mode' entry keyed to [strongModeKey] with a 'language'
286+
/// entry with [languageKey] and [languageValue].
287+
///
288+
/// 'strong-mode' and 'language' are each maps which can be found under the
289+
/// top-level 'analyzer' map. 'strong-mode' (given as [strongModeMap]) must
290+
/// already be present under the 'analyzer' map (given as [analyzerMap]).
291+
void _replaceStrongModeEntryWithLanguageEntry(
292+
YamlFileEditBuilder builder,
293+
List<YamlNode> coveringNodePath,
294+
YamlMap analyzerMap,
295+
YamlMap strongModeMap, {
296+
required String strongModeKey,
297+
required String languageKey,
298+
required Object? languageValue,
299+
}) {
300+
var yamlEditor = YamlEditor(content);
301+
// If 'language' does not exist yet under 'analyzer', create it.
302+
if (analyzerMap['language'] == null) {
303+
yamlEditor.update(['analyzer', 'language'], {languageKey: languageValue});
304+
} else {
305+
yamlEditor.update(['analyzer', 'language', languageKey], languageValue);
306+
}
307+
var languageEdit = yamlEditor.edits.single;
308+
builder.addSimpleReplacement(
309+
SourceRange(languageEdit.offset, languageEdit.length),
310+
languageEdit.replacement);
311+
312+
// If `strongModeKey` is the only entry under 'strong-mode', then remove
313+
// the entire 'strong-mode' entry.
314+
if (strongModeMap.length == 1) {
315+
yamlEditor.remove(['analyzer', 'strong-mode']);
316+
} else {
317+
yamlEditor.remove(['analyzer', 'strong-mode', strongModeKey]);
318+
}
319+
var strongModeEdit = yamlEditor.edits[1];
320+
int strongModeEditOffset;
321+
if (strongModeEdit.offset > languageEdit.offset) {
322+
strongModeEditOffset = strongModeEdit.offset -
323+
(languageEdit.replacement.length - languageEdit.length);
324+
} else {
325+
strongModeEditOffset = strongModeEdit.offset;
326+
}
327+
builder.addSimpleReplacement(
328+
SourceRange(strongModeEditOffset, strongModeEdit.length),
329+
strongModeEdit.replacement);
330+
}
202331
}
203332

204333
class _NonDartChangeWorkspace implements ChangeWorkspace {

pkg/analysis_server/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies:
2929
path: any
3030
watcher: any
3131
yaml: any
32+
yaml_edit: any
3233

3334
dev_dependencies:
3435
analyzer_utilities:

pkg/analysis_server/test/abstract_context.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ class AbstractContextTest with ResourceProviderMixin {
6666

6767
Future<AnalysisSession> get session => sessionFor(testPackageRootPath);
6868

69+
/// The path for `analysis_options.yaml` in [testPackageRootPath].
70+
String get testAnalysisOptionsPath =>
71+
convertPath('$testPackageRootPath/analysis_options.yaml');
72+
6973
String? get testPackageLanguageVersion => latestLanguageVersion;
7074

7175
String get testPackageLibPath => '$testPackageRootPath/lib';

pkg/analysis_server/test/domain_completion_test.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,13 @@ void main() {
4343
/// TODO(scheglov) this is duplicate
4444
class AnalysisOptionsFileConfig {
4545
final List<String> experiments;
46-
final bool implicitCasts;
47-
final bool implicitDynamic;
4846
final List<String> lints;
4947
final bool strictCasts;
5048
final bool strictInference;
5149
final bool strictRawTypes;
5250

5351
AnalysisOptionsFileConfig({
5452
this.experiments = const [],
55-
this.implicitCasts = true,
56-
this.implicitDynamic = true,
5753
this.lints = const [],
5854
this.strictCasts = false,
5955
this.strictInference = false,
@@ -72,9 +68,6 @@ class AnalysisOptionsFileConfig {
7268
buffer.writeln(' strict-casts: $strictCasts');
7369
buffer.writeln(' strict-inference: $strictInference');
7470
buffer.writeln(' strict-raw-types: $strictRawTypes');
75-
buffer.writeln(' strong-mode:');
76-
buffer.writeln(' implicit-casts: $implicitCasts');
77-
buffer.writeln(' implicit-dynamic: $implicitDynamic');
7871

7972
buffer.writeln('linter:');
8073
buffer.writeln(' rules:');
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright (c) 2022, 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:test_reflective_loader/test_reflective_loader.dart';
6+
7+
import 'test_support.dart';
8+
9+
void main() {
10+
defineReflectiveSuite(() {
11+
defineReflectiveTests(ReplaceWithStrictCastsTest);
12+
});
13+
}
14+
15+
@reflectiveTest
16+
class ReplaceWithStrictCastsTest extends AnalysisOptionsFixTest {
17+
Future<void> test_hasLanguage() async {
18+
await assertHasFix('''
19+
analyzer:
20+
language:
21+
strict-inference: true
22+
strong-mode:
23+
implicit-casts: false
24+
''', '''
25+
analyzer:
26+
language:
27+
strict-casts: true
28+
strict-inference: true
29+
''');
30+
}
31+
32+
Future<void> test_hasLanguage_isAfter() async {
33+
await assertHasFix('''
34+
analyzer:
35+
strong-mode:
36+
implicit-casts: false
37+
language:
38+
strict-inference: true
39+
''', '''
40+
analyzer:
41+
language:
42+
strict-casts: true
43+
strict-inference: true
44+
''');
45+
}
46+
47+
Future<void> test_hasStrictCastsFalse() async {
48+
await assertHasFix('''
49+
analyzer:
50+
language:
51+
strict-casts: false
52+
strong-mode:
53+
implicit-casts: false
54+
''', '''
55+
analyzer:
56+
language:
57+
strict-casts: true
58+
''');
59+
}
60+
61+
Future<void> test_hasStrictCastsTrue() async {
62+
await assertHasFix('''
63+
analyzer:
64+
language:
65+
strict-casts: true
66+
strong-mode:
67+
implicit-casts: false
68+
''', '''
69+
analyzer:
70+
language:
71+
strict-casts: true
72+
''');
73+
}
74+
75+
Future<void> test_noLanguage() async {
76+
await assertHasFix('''
77+
analyzer:
78+
strong-mode:
79+
implicit-casts: false
80+
''', '''
81+
analyzer:
82+
language:
83+
strict-casts: true
84+
''');
85+
}
86+
87+
Future<void> test_noLanguage_analyzerHasOtherEntries() async {
88+
await assertHasFix('''
89+
analyzer:
90+
errors:
91+
unused_import: ignore
92+
strong-mode:
93+
implicit-casts: false
94+
''', '''
95+
analyzer:
96+
errors:
97+
unused_import: ignore
98+
language:
99+
strict-casts: true
100+
''');
101+
}
102+
103+
Future<void> test_noLanguage_analyzerHasOtherEntriesAfter() async {
104+
await assertHasFix('''
105+
analyzer:
106+
strong-mode:
107+
implicit-casts: false
108+
errors:
109+
unused_import: ignore
110+
''', '''
111+
analyzer:
112+
errors:
113+
unused_import: ignore
114+
language:
115+
strict-casts: true
116+
''');
117+
}
118+
119+
Future<void> test_noLanguage_hasOtherStrongModeEntry() async {
120+
await assertHasFix('''
121+
analyzer:
122+
strong-mode:
123+
implicit-casts: false
124+
implicit-dynamic: false
125+
''', '''
126+
analyzer:
127+
language:
128+
strict-casts: true
129+
strong-mode:
130+
implicit-dynamic: false
131+
''', errorFilter: (error) => error.message.contains('implicit-casts'));
132+
}
133+
134+
Future<void> test_noLanguage_implicitCastsHasComment() async {
135+
await assertHasFix('''
136+
analyzer:
137+
strong-mode:
138+
# No implicit casts
139+
implicit-casts: false
140+
''', '''
141+
analyzer:
142+
language:
143+
strict-casts: true
144+
''');
145+
}
146+
147+
Future<void> test_noLanguage_strongModeHasComment() async {
148+
// TODO(srawlins): This is unfortunate; it would be better to remove the
149+
// comment. But we leave this assertion as is to show at least the file is
150+
// not corrupted.
151+
await assertHasFix('''
152+
analyzer:
153+
# Strong mode
154+
strong-mode:
155+
implicit-casts: false
156+
''', '''
157+
analyzer:
158+
# Strong mode
159+
language:
160+
strict-casts: true
161+
''');
162+
}
163+
}

0 commit comments

Comments
 (0)