mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 15:29:45 +00:00
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
This reverts 5050f31b29
Change-Id: Icd156b0dda78a50ed28272ddef7460018f511cc2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/276766
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
778e8d13b2
commit
166c54b4d9
|
@ -43,6 +43,10 @@ AnalysisOptionsErrorCode.PARSE_ERROR:
|
|||
AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED:
|
||||
status: needsFix
|
||||
notes: Fixed.
|
||||
AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED:
|
||||
status: needsEvaluation
|
||||
AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT:
|
||||
status: needsEvaluation
|
||||
AnalysisOptionsWarningCode.INCLUDE_FILE_NOT_FOUND:
|
||||
status: noFix
|
||||
notes: |-
|
||||
|
|
|
@ -40,6 +40,16 @@ class AnalysisOptionsFixKind {
|
|||
50,
|
||||
"Remove '{0}'",
|
||||
);
|
||||
static const REPLACE_WITH_STRICT_CASTS = FixKind(
|
||||
'analysisOptions.fix.replaceWithStrictCasts',
|
||||
50,
|
||||
'Replace with the strict-casts analysis mode',
|
||||
);
|
||||
static const REPLACE_WITH_STRICT_RAW_TYPES = FixKind(
|
||||
'analysisOptions.fix.replaceWithStrictRawTypes',
|
||||
50,
|
||||
'Replace with the strict-raw-types analysis mode',
|
||||
);
|
||||
}
|
||||
|
||||
/// The implementation of [DartFixContext].
|
||||
|
|
|
@ -16,9 +16,12 @@ import 'package:analyzer/src/generated/java_core.dart';
|
|||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer/src/lint/options_rule_validator.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_yaml.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
|
||||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
import 'package:yaml_edit/yaml_edit.dart';
|
||||
|
||||
/// The generator used to generate fixes in analysis options files.
|
||||
class AnalysisOptionsFixGenerator {
|
||||
|
@ -64,6 +67,25 @@ class AnalysisOptionsFixGenerator {
|
|||
// } else
|
||||
if (errorCode == AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED) {
|
||||
await _addFix_removeSetting(coveringNodePath);
|
||||
} else if (errorCode ==
|
||||
AnalysisOptionsWarningCode
|
||||
.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT) {
|
||||
var analyzerMap = options['analyzer'];
|
||||
if (analyzerMap is! YamlMap) {
|
||||
return fixes;
|
||||
}
|
||||
var strongModeMap = analyzerMap['strong-mode'];
|
||||
|
||||
if (strongModeMap is! YamlMap) {
|
||||
return fixes;
|
||||
}
|
||||
if (_isErrorAtMapKey(strongModeMap, 'implicit-casts')) {
|
||||
await _addFix_replaceWithStrictCasts(
|
||||
coveringNodePath, analyzerMap, strongModeMap);
|
||||
} else if (_isErrorAtMapKey(strongModeMap, 'implicit-dynamic')) {
|
||||
await _addFix_replaceWithStrictRawTypes(
|
||||
coveringNodePath, analyzerMap, strongModeMap);
|
||||
}
|
||||
} else if (errorCode == DEPRECATED_LINT_HINT) {
|
||||
await _addFix_removeLint(coveringNodePath);
|
||||
// } else if (errorCode == AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING) {
|
||||
|
@ -101,6 +123,52 @@ class AnalysisOptionsFixGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
/// Replaces `analyzer: strong-mode: implicit-casts: false` with
|
||||
/// `analyzer: language: strict-casts: true`.
|
||||
Future<void> _addFix_replaceWithStrictCasts(List<YamlNode> coveringNodePath,
|
||||
YamlMap analyzerMap, YamlMap strongModeMap) async {
|
||||
var builder =
|
||||
ChangeBuilder(workspace: _NonDartChangeWorkspace(resourceProvider));
|
||||
await builder.addYamlFileEdit(file, (builder) {
|
||||
_replaceStrongModeEntryWithLanguageEntry(
|
||||
builder,
|
||||
coveringNodePath,
|
||||
analyzerMap,
|
||||
strongModeMap,
|
||||
strongModeKey: 'implicit-casts',
|
||||
languageKey: 'strict-casts',
|
||||
languageValue: true,
|
||||
);
|
||||
});
|
||||
_addFixFromBuilder(
|
||||
builder, AnalysisOptionsFixKind.REPLACE_WITH_STRICT_CASTS,
|
||||
args: [coveringNodePath[0].toString()]);
|
||||
}
|
||||
|
||||
/// Replaces `analyzer: strong-mode: implicit-dynamic: false` with
|
||||
/// `analyzer: language: strict-raw-types: true`.
|
||||
Future<void> _addFix_replaceWithStrictRawTypes(
|
||||
List<YamlNode> coveringNodePath,
|
||||
YamlMap analyzerMap,
|
||||
YamlMap strongModeMap) async {
|
||||
var builder =
|
||||
ChangeBuilder(workspace: _NonDartChangeWorkspace(resourceProvider));
|
||||
await builder.addYamlFileEdit(file, (builder) {
|
||||
_replaceStrongModeEntryWithLanguageEntry(
|
||||
builder,
|
||||
coveringNodePath,
|
||||
analyzerMap,
|
||||
strongModeMap,
|
||||
strongModeKey: 'implicit-dynamic',
|
||||
languageKey: 'strict-raw-types',
|
||||
languageValue: true,
|
||||
);
|
||||
});
|
||||
_addFixFromBuilder(
|
||||
builder, AnalysisOptionsFixKind.REPLACE_WITH_STRICT_RAW_TYPES,
|
||||
args: [coveringNodePath[0].toString()]);
|
||||
}
|
||||
|
||||
/// Add a fix whose edits were built by the [builder] that has the given
|
||||
/// [kind]. If [args] are provided, they will be used to fill in the message
|
||||
/// for the fix.
|
||||
|
@ -184,6 +252,20 @@ class AnalysisOptionsFixGenerator {
|
|||
return offset;
|
||||
}
|
||||
|
||||
/// Returns whether the error is located within [map], covering the
|
||||
/// [YamlScalar] node for [key].
|
||||
bool _isErrorAtMapKey(YamlMap map, String key) {
|
||||
var keyNode = map.nodes.keys
|
||||
.whereType<YamlScalar>()
|
||||
.firstWhereOrNull((k) => k.value == key);
|
||||
if (keyNode == null) {
|
||||
return false;
|
||||
}
|
||||
var keyOffset = keyNode.span.start.offset;
|
||||
var keyLength = keyNode.span.end.offset - keyOffset;
|
||||
return keyOffset == errorOffset && keyLength == errorLength;
|
||||
}
|
||||
|
||||
SourceRange _lines(int start, int end) {
|
||||
var startLocation = lineInfo.getLocation(start);
|
||||
var startOffset = lineInfo.getOffsetOfLine(startLocation.lineNumber - 1);
|
||||
|
@ -192,6 +274,53 @@ class AnalysisOptionsFixGenerator {
|
|||
math.min(endLocation.lineNumber, lineInfo.lineCount - 1));
|
||||
return SourceRange(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
/// Replaces a 'strong-mode' entry keyed to [strongModeKey] with a 'language'
|
||||
/// entry with [languageKey] and [languageValue].
|
||||
///
|
||||
/// 'strong-mode' and 'language' are each maps which can be found under the
|
||||
/// top-level 'analyzer' map. 'strong-mode' (given as [strongModeMap]) must
|
||||
/// already be present under the 'analyzer' map (given as [analyzerMap]).
|
||||
void _replaceStrongModeEntryWithLanguageEntry(
|
||||
YamlFileEditBuilder builder,
|
||||
List<YamlNode> coveringNodePath,
|
||||
YamlMap analyzerMap,
|
||||
YamlMap strongModeMap, {
|
||||
required String strongModeKey,
|
||||
required String languageKey,
|
||||
required Object? languageValue,
|
||||
}) {
|
||||
var yamlEditor = YamlEditor(content);
|
||||
// If 'language' does not exist yet under 'analyzer', create it.
|
||||
if (analyzerMap['language'] == null) {
|
||||
yamlEditor.update(['analyzer', 'language'], {languageKey: languageValue});
|
||||
} else {
|
||||
yamlEditor.update(['analyzer', 'language', languageKey], languageValue);
|
||||
}
|
||||
var languageEdit = yamlEditor.edits.single;
|
||||
builder.addSimpleReplacement(
|
||||
SourceRange(languageEdit.offset, languageEdit.length),
|
||||
languageEdit.replacement);
|
||||
|
||||
// If `strongModeKey` is the only entry under 'strong-mode', then remove
|
||||
// the entire 'strong-mode' entry.
|
||||
if (strongModeMap.length == 1) {
|
||||
yamlEditor.remove(['analyzer', 'strong-mode']);
|
||||
} else {
|
||||
yamlEditor.remove(['analyzer', 'strong-mode', strongModeKey]);
|
||||
}
|
||||
var strongModeEdit = yamlEditor.edits[1];
|
||||
int strongModeEditOffset;
|
||||
if (strongModeEdit.offset > languageEdit.offset) {
|
||||
strongModeEditOffset = strongModeEdit.offset -
|
||||
(languageEdit.replacement.length - languageEdit.length);
|
||||
} else {
|
||||
strongModeEditOffset = strongModeEdit.offset;
|
||||
}
|
||||
builder.addSimpleReplacement(
|
||||
SourceRange(strongModeEditOffset, strongModeEdit.length),
|
||||
strongModeEdit.replacement);
|
||||
}
|
||||
}
|
||||
|
||||
class _NonDartChangeWorkspace implements ChangeWorkspace {
|
||||
|
|
|
@ -24,6 +24,7 @@ dependencies:
|
|||
test: any
|
||||
watcher: any
|
||||
yaml: any
|
||||
yaml_edit: any
|
||||
|
||||
# Use 'any' constraints here; we get our versions from the DEPS file.
|
||||
dev_dependencies:
|
||||
|
|
|
@ -65,6 +65,10 @@ class AbstractContextTest with ResourceProviderMixin {
|
|||
|
||||
Future<AnalysisSession> get session => sessionFor(testPackageRootPath);
|
||||
|
||||
/// The path for `analysis_options.yaml` in [testPackageRootPath].
|
||||
String get testAnalysisOptionsPath =>
|
||||
convertPath('$testPackageRootPath/analysis_options.yaml');
|
||||
|
||||
String? get testPackageLanguageVersion => latestLanguageVersion;
|
||||
|
||||
String get testPackageLibPath => '$testPackageRootPath/lib';
|
||||
|
|
|
@ -58,9 +58,15 @@ class AnalysisOptionsFileConfig {
|
|||
buffer.writeln(' strict-casts: $strictCasts');
|
||||
buffer.writeln(' strict-inference: $strictInference');
|
||||
buffer.writeln(' strict-raw-types: $strictRawTypes');
|
||||
buffer.writeln(' strong-mode:');
|
||||
buffer.writeln(' implicit-casts: $implicitCasts');
|
||||
buffer.writeln(' implicit-dynamic: $implicitDynamic');
|
||||
if (!implicitCasts || !implicitDynamic) {
|
||||
buffer.writeln(' strong-mode:');
|
||||
if (!implicitCasts) {
|
||||
buffer.writeln(' implicit-casts: $implicitCasts');
|
||||
}
|
||||
if (!implicitDynamic) {
|
||||
buffer.writeln(' implicit-dynamic: $implicitDynamic');
|
||||
}
|
||||
}
|
||||
|
||||
buffer.writeln('linter:');
|
||||
buffer.writeln(' rules:');
|
||||
|
|
|
@ -271,7 +271,7 @@ class BasicCompletionTest2 extends AbstractCompletionDriverTest
|
|||
}
|
||||
|
||||
mixin BasicCompletionTestCases on AbstractCompletionDriverTest {
|
||||
/// Duplicates (and potentially replaces DeprecatedMemberRelevanceTest).
|
||||
/// Duplicates (and potentially replaces) [DeprecatedMemberRelevanceTest].
|
||||
Future<void> test_deprecated_member_relevance() async {
|
||||
await addTestFile('''
|
||||
class A {
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(ReplaceWithStrictCastsTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class ReplaceWithStrictCastsTest extends AnalysisOptionsFixTest {
|
||||
Future<void> test_hasLanguage() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-inference: true
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasLanguage_isAfter() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
language:
|
||||
strict-inference: true
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasStrictCastsFalse() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: false
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasStrictCastsTrue() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_analyzerHasOtherEntries() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_analyzerHasOtherEntriesAfter() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
errors:
|
||||
unused_import: ignore
|
||||
''', '''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_hasOtherStrongModeEntry() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', errorFilter: (error) => error.message.contains('implicit-casts'));
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_implicitCastsHasComment() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
# No implicit casts
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_strongModeHasComment() async {
|
||||
// TODO(srawlins): This is unfortunate; it would be better to remove the
|
||||
// comment. But we leave this assertion as is to show at least the file is
|
||||
// not corrupted.
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
# Strong mode
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', '''
|
||||
analyzer:
|
||||
# Strong mode
|
||||
language:
|
||||
strict-casts: true
|
||||
''');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(ReplaceWithStrictRawTypesTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class ReplaceWithStrictRawTypesTest extends AnalysisOptionsFixTest {
|
||||
Future<void> test_hasLanguage() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-inference: true
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasLanguage_isAfter() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
language:
|
||||
strict-inference: true
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasStrictRawTypesFalse() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: false
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_hasStrictRawTypesTrue() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_analyzerHasOtherEntries() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_analyzerHasOtherEntriesAfter() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
errors:
|
||||
unused_import: ignore
|
||||
''', '''
|
||||
analyzer:
|
||||
errors:
|
||||
unused_import: ignore
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_hasOtherStrongModeEntry() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', errorFilter: (error) => error.message.contains('implicit-dynamic'));
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_implicitDynamicHasComment() async {
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
# No implicit dynamic
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_noLanguage_strongModeHasComment() async {
|
||||
// TODO(srawlins): This is unfortunate; it would be better to remove the
|
||||
// comment. But we leave this assertion as is to show at least the file is
|
||||
// not corrupted.
|
||||
await assertHasFix('''
|
||||
analyzer:
|
||||
# Strong mode
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', '''
|
||||
analyzer:
|
||||
# Strong mode
|
||||
language:
|
||||
strict-raw-types: true
|
||||
''');
|
||||
}
|
||||
}
|
|
@ -6,10 +6,15 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|||
|
||||
import 'remove_lint_test.dart' as remove_lint;
|
||||
import 'remove_setting_test.dart' as remove_setting;
|
||||
import 'replace_with_strict_casts_test.dart' as replace_with_strict_casts;
|
||||
import 'replace_with_strict_raw_types_test.dart'
|
||||
as replace_with_strict_raw_types;
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
remove_lint.main();
|
||||
remove_setting.main();
|
||||
replace_with_strict_casts.main();
|
||||
replace_with_strict_raw_types.main();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
|
||||
import 'package:analysis_server/src/protocol_server.dart' show SourceEdit;
|
||||
import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer/src/task/options.dart';
|
||||
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol_common.dart'
|
||||
hide AnalysisError;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
|
@ -16,8 +17,11 @@ import 'package:yaml/yaml.dart';
|
|||
/// errors in Dart files.
|
||||
class AnalysisOptionsFixTest with ResourceProviderMixin {
|
||||
Future<void> assertHasFix(
|
||||
String initialContent, String expectedContent) async {
|
||||
var fixes = await _getFixes(initialContent);
|
||||
String initialContent,
|
||||
String expectedContent, {
|
||||
bool Function(AnalysisError)? errorFilter,
|
||||
}) async {
|
||||
var fixes = await _getFixes(initialContent, errorFilter: errorFilter);
|
||||
expect(fixes, hasLength(1));
|
||||
var fileEdits = fixes[0].change.edits;
|
||||
expect(fileEdits, hasLength(1));
|
||||
|
@ -32,8 +36,11 @@ class AnalysisOptionsFixTest with ResourceProviderMixin {
|
|||
expect(fixes, hasLength(0));
|
||||
}
|
||||
|
||||
Future<List<Fix>> _getFixes(String content) {
|
||||
var optionsFile = getFile('/analysis_options.yaml');
|
||||
Future<List<Fix>> _getFixes(
|
||||
String content, {
|
||||
bool Function(AnalysisError)? errorFilter,
|
||||
}) {
|
||||
var optionsFile = newFile('/analysis_options.yaml', content);
|
||||
var sourceFactory = SourceFactory([]);
|
||||
var errors = analyzeAnalysisOptions(
|
||||
optionsFile.createSource(),
|
||||
|
@ -41,6 +48,12 @@ class AnalysisOptionsFixTest with ResourceProviderMixin {
|
|||
sourceFactory,
|
||||
'/',
|
||||
);
|
||||
if (errorFilter != null) {
|
||||
if (errors.length == 1) {
|
||||
fail('Unnecessary error filter');
|
||||
}
|
||||
errors = errors.where(errorFilter).toList();
|
||||
}
|
||||
expect(errors, hasLength(1));
|
||||
var error = errors[0];
|
||||
var options = _parseYaml(content);
|
||||
|
|
|
@ -90,6 +90,30 @@ class AnalysisOptionsHintCode extends ErrorCode {
|
|||
}
|
||||
|
||||
class AnalysisOptionsWarningCode extends ErrorCode {
|
||||
/// An error code indicating that the given option is deprecated.
|
||||
///
|
||||
/// Parameters:
|
||||
/// 0: the option name
|
||||
///
|
||||
static const AnalysisOptionsWarningCode ANALYSIS_OPTION_DEPRECATED =
|
||||
AnalysisOptionsWarningCode(
|
||||
'ANALYSIS_OPTION_DEPRECATED',
|
||||
"The option '{0}' is no longer supported.",
|
||||
);
|
||||
|
||||
/// An error code indicating that the given option is deprecated.
|
||||
///
|
||||
/// Parameters:
|
||||
/// 0: the option name
|
||||
/// 1: the replacement option name
|
||||
static const AnalysisOptionsWarningCode
|
||||
ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT = AnalysisOptionsWarningCode(
|
||||
'ANALYSIS_OPTION_DEPRECATED',
|
||||
"The option '{0}' is no longer supported.",
|
||||
correctionMessage: "Try using the new '{1}' option.",
|
||||
uniqueName: 'ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT',
|
||||
);
|
||||
|
||||
/// An error code indicating a specified include file has a warning.
|
||||
///
|
||||
/// Parameters:
|
||||
|
|
|
@ -23,6 +23,8 @@ const List<ErrorCode> errorCodeValues = [
|
|||
AnalysisOptionsErrorCode.INCLUDED_FILE_PARSE_ERROR,
|
||||
AnalysisOptionsErrorCode.PARSE_ERROR,
|
||||
AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED,
|
||||
AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED,
|
||||
AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT,
|
||||
AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING,
|
||||
AnalysisOptionsWarningCode.INCLUDE_FILE_NOT_FOUND,
|
||||
AnalysisOptionsWarningCode.INVALID_OPTION,
|
||||
|
|
|
@ -625,38 +625,48 @@ class StrongModeOptionValueValidator extends OptionsValidator {
|
|||
void validate(ErrorReporter reporter, YamlMap options) {
|
||||
var analyzer = options.valueAt(AnalyzerOptions.analyzer);
|
||||
if (analyzer is YamlMap) {
|
||||
var v = analyzer.valueAt(AnalyzerOptions.strongMode);
|
||||
if (v is YamlScalar) {
|
||||
var value = toLowerCase(v.value);
|
||||
if (!AnalyzerOptions.trueOrFalse.contains(value)) {
|
||||
var strongModeNode = analyzer.valueAt(AnalyzerOptions.strongMode);
|
||||
if (strongModeNode is YamlScalar) {
|
||||
return _validateStrongModeAsScalar(reporter, strongModeNode);
|
||||
} else if (strongModeNode is YamlMap) {
|
||||
return _validateStrongModeAsMap(reporter, strongModeNode);
|
||||
} else if (strongModeNode != null) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
|
||||
strongModeNode.span,
|
||||
[AnalyzerOptions.strongMode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _validateStrongModeAsMap(
|
||||
ErrorReporter reporter, YamlMap strongModeNode) {
|
||||
strongModeNode.nodes.forEach((k, v) {
|
||||
if (k is YamlScalar) {
|
||||
var key = k.value?.toString();
|
||||
if (!AnalyzerOptions.strongModeOptions.contains(key)) {
|
||||
_builder.reportError(reporter, AnalyzerOptions.strongMode, k);
|
||||
} else if (key == AnalyzerOptions.declarationCasts) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, v.span, [
|
||||
AnalyzerOptions.strongMode,
|
||||
v.value,
|
||||
AnalyzerOptions.trueOrFalseProposal
|
||||
]);
|
||||
} else if (value == 'false') {
|
||||
AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED,
|
||||
k.span,
|
||||
[AnalyzerOptions.declarationCasts]);
|
||||
} else if (key == AnalyzerOptions.implicitCasts) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.SPEC_MODE_REMOVED, v.span);
|
||||
} else if (value == 'true') {
|
||||
AnalysisOptionsWarningCode
|
||||
.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT,
|
||||
k.span,
|
||||
[AnalyzerOptions.implicitCasts, 'strict-casts']);
|
||||
} else if (key == AnalyzerOptions.implicitDynamic) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED, v.span);
|
||||
}
|
||||
} else if (v is YamlMap) {
|
||||
v.nodes.forEach((k, v) {
|
||||
String? key, value;
|
||||
bool validKey = false;
|
||||
if (k is YamlScalar) {
|
||||
key = k.value?.toString();
|
||||
if (!AnalyzerOptions.strongModeOptions.contains(key)) {
|
||||
_builder.reportError(reporter, AnalyzerOptions.strongMode, k);
|
||||
} else if (key != AnalyzerOptions.declarationCasts) {
|
||||
// If we have a valid key, go on and check the value.
|
||||
validKey = true;
|
||||
}
|
||||
}
|
||||
if (validKey && v is YamlScalar) {
|
||||
value = toLowerCase(v.value);
|
||||
AnalysisOptionsWarningCode
|
||||
.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT,
|
||||
k.span,
|
||||
[AnalyzerOptions.implicitDynamic, 'strict-raw-types']);
|
||||
} else {
|
||||
// The key is valid.
|
||||
if (v is YamlScalar) {
|
||||
var value = toLowerCase(v.value);
|
||||
if (!AnalyzerOptions.trueOrFalse.contains(value)) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.UNSUPPORTED_VALUE,
|
||||
|
@ -664,13 +674,28 @@ class StrongModeOptionValueValidator extends OptionsValidator {
|
|||
[key!, v.value, AnalyzerOptions.trueOrFalseProposal]);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (v != null) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
|
||||
v.span,
|
||||
[AnalyzerOptions.enableExperiment]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _validateStrongModeAsScalar(
|
||||
ErrorReporter reporter, YamlScalar strongModeNode) {
|
||||
var stringValue = toLowerCase(strongModeNode.value);
|
||||
if (!AnalyzerOptions.trueOrFalse.contains(stringValue)) {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.UNSUPPORTED_VALUE, strongModeNode.span, [
|
||||
AnalyzerOptions.strongMode,
|
||||
strongModeNode.value,
|
||||
AnalyzerOptions.trueOrFalseProposal
|
||||
]);
|
||||
} else if (stringValue == 'false') {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsWarningCode.SPEC_MODE_REMOVED, strongModeNode.span);
|
||||
} else if (stringValue == 'true') {
|
||||
reporter.reportErrorForSpan(
|
||||
AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED,
|
||||
strongModeNode.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,23 @@ AnalysisOptionsHintCode:
|
|||
correctionMessage: It is no longer necessary to explicitly enable strong mode.
|
||||
comment: "An error code indicating that strong-mode: true is deprecated."
|
||||
AnalysisOptionsWarningCode:
|
||||
ANALYSIS_OPTION_DEPRECATED:
|
||||
problemMessage: "The option '{0}' is no longer supported."
|
||||
comment: |
|
||||
An error code indicating that the given option is deprecated.
|
||||
|
||||
Parameters:
|
||||
0: the option name
|
||||
ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT:
|
||||
sharedName: ANALYSIS_OPTION_DEPRECATED
|
||||
problemMessage: "The option '{0}' is no longer supported."
|
||||
correctionMessage: "Try using the new '{1}' option."
|
||||
comment: |-
|
||||
An error code indicating that the given option is deprecated.
|
||||
|
||||
Parameters:
|
||||
0: the option name
|
||||
1: the replacement option name
|
||||
INCLUDED_FILE_WARNING:
|
||||
problemMessage: "Warning in the included options file {0}({1}..{2}): {3}"
|
||||
comment: |-
|
||||
|
|
|
@ -452,6 +452,30 @@ analyzer:
|
|||
''', [AnalysisOptionsHintCode.STRONG_MODE_SETTING_DEPRECATED]);
|
||||
}
|
||||
|
||||
test_analyzer_strong_mode_deprecated_key() {
|
||||
validate('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
declaration-casts: false
|
||||
''', [AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED]);
|
||||
}
|
||||
|
||||
test_analyzer_strong_mode_deprecated_key_implicit_casts() {
|
||||
validate('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
''', [AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT]);
|
||||
}
|
||||
|
||||
test_analyzer_strong_mode_deprecated_key_implicit_dynamic() {
|
||||
validate('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-dynamic: false
|
||||
''', [AnalysisOptionsWarningCode.ANALYSIS_OPTION_DEPRECATED_WITH_REPLACEMENT]);
|
||||
}
|
||||
|
||||
test_analyzer_strong_mode_error_code_supported() {
|
||||
validate('''
|
||||
analyzer:
|
||||
|
@ -483,14 +507,6 @@ analyzer:
|
|||
''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]);
|
||||
}
|
||||
|
||||
test_analyzer_strong_mode_unsupported_value() {
|
||||
validate('''
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-dynamic: foo
|
||||
''', [AnalysisOptionsWarningCode.UNSUPPORTED_VALUE]);
|
||||
}
|
||||
|
||||
test_analyzer_supported_exclude() {
|
||||
validate('''
|
||||
analyzer:
|
||||
|
|
|
@ -188,11 +188,7 @@ class ChangeBuilderImpl implements ChangeBuilder {
|
|||
this,
|
||||
path,
|
||||
loadYamlDocument(
|
||||
workspace
|
||||
.getSession(path)!
|
||||
.resourceProvider
|
||||
.getFile(path)
|
||||
.readAsStringSync(),
|
||||
workspace.resourceProvider.getFile(path).readAsStringSync(),
|
||||
recover: true),
|
||||
0);
|
||||
_yamlFileEditBuilders[path] = builder;
|
||||
|
|
Loading…
Reference in a new issue