Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions lib/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ solid_lints:
allow_initialized: true
ignored_types:
- AnimationController
avoid_non_null_assertion:
ignored_types:
- IMap
- BuiltMap
avoid_non_null_assertion: true
avoid_returning_widgets: true
avoid_similar_names: true
avoid_unnecessary_return_variable: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// Rule which warns about usages of bang operator ("!")
/// as it may result in unexpected runtime exceptions.
///
/// "Bang" operator with Maps is allowed, as [Dart docs](https://dart.dev/null-safety/understanding-null-safety#the-map-index-operator-is-nullable)
/// recommend using it for accessing Map values that are known to be present.
/// Types with index operator (like [Map], `IMap`, `BuiltMap`) can be ignored
/// using `ignored_types` config parameter.
///
/// ### Example config:
///
Expand All @@ -18,6 +18,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// diagnostics:
/// avoid_non_null_assertion:
/// ignored_types:
/// - Map
/// - IMap
/// - BuiltMap
/// ```
Expand All @@ -28,9 +29,11 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// ```dart
/// Object? object;
/// int? number;
/// final map = {'key': 'value'};
///
/// final int computed = 1 + number!; // LINT
/// object!.method(); // LINT
/// map['key']!; // LINT (unless Map is in ignored_types)
/// ```
///
/// #### GOOD:
Expand All @@ -43,7 +46,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// }
/// object?.method();
///
/// // No lint on maps
/// // Allowed if Map is in ignored_types
/// final map = {'key': 'value'};
/// map['key']!;
/// ```
Expand All @@ -62,8 +65,7 @@ class AvoidNonNullAssertionRule
AvoidNonNullAssertionRule({required super.analysisOptionsLoader})
: super.withParameters(
name: lintName,
description:
'Warns about usages of bang operator (!) except valid Map access.',
description: 'Warns about usages of bang operator (!).',
parametersParser: AvoidNonNullAssertionParameters.fromJson,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ class AvoidNonNullAssertionParameters {
/// diagnostics:
/// avoid_non_null_assertion:
/// ignored_types:
/// - Map
/// - IMap
/// ```
///
/// ```dart
/// IMap<String, String> map;
/// Map<String, String> map;
/// map['key']!; // OK
/// ```
final Set<String> ignoredTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,14 @@ class AvoidNonNullAssertionVisitor extends SimpleAstVisitor<void> {
if (operand is IndexExpression) {
final type = operand.target?.staticType;

if (_isMap(type) || _hasIgnoredType(type)) {
if (_hasIgnoredType(type)) {
return;
}
}

rule.reportAtNode(node);
}

bool _isMap(DartType? type) {
if (type is! InterfaceType) {
return false;
}

return type.isDartCoreMap || type.allSupertypes.any((v) => v.isDartCoreMap);
}

bool _hasIgnoredType(DartType? type) {
if (type == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ plugins:
diagnostics:
avoid_non_null_assertion:
ignored_types:
- Map
- IMap
- BuiltMap
''';
Expand Down Expand Up @@ -57,6 +58,32 @@ void m(Object? object) {
''');
}

Future<void> test_reports_map_access_when_not_ignored() async {
newAnalysisOptionsYamlFile(
testPackageRootPath,
analysisOptionsContent(rules: [rule.name]),
);
await assertAutoDiagnostics('''
void m() {
final map = {'key': 'value'};
${expectLint("map['key']!")};
}
''');
}

Future<void> test_reports_parenthesized_map_access_when_not_ignored() async {
newAnalysisOptionsYamlFile(
testPackageRootPath,
analysisOptionsContent(rules: [rule.name]),
);
await assertAutoDiagnostics('''
void m() {
final map = {'key': 'value'};
${expectLint("(map['key'])!")};
}
''');
}

Future<void> test_does_not_report_map_access() async {
await assertNoDiagnostics(r'''
void m() {
Expand All @@ -66,6 +93,15 @@ void m() {
''');
}

Future<void> test_does_not_report_parenthesized_map_access() async {
await assertNoDiagnostics(r'''
void m() {
final map = {'key': 'value'};
(map['key'])!;
}
''');
}

Future<void> test_does_not_report_safe_null_check() async {
await assertNoDiagnostics(r'''
void m(int? number) {
Expand All @@ -88,15 +124,6 @@ void m(IMap<String, String> map) {
''');
}

Future<void> test_does_not_report_parenthesized_map_access() async {
await assertNoDiagnostics(r'''
void m() {
final map = {'key': 'value'};
(map['key'])!;
}
''');
}

Future<void> test_does_not_report_imap_access_with_single_string() async {
newAnalysisOptionsYamlFile(
testPackageRootPath,
Expand Down