From 2ab63ad1a2087203f4b17d8ce91f9819009b13b5 Mon Sep 17 00:00:00 2001 From: Illia Aihistov Date: Wed, 19 Aug 2026 19:28:03 +0300 Subject: [PATCH] refactor: remove default exception for Map types in avoid_non_null_assertion lint to support custom configuration --- lib/analysis_options.yaml | 5 +-- .../avoid_non_null_assertion_rule.dart | 12 ++--- .../avoid_non_null_assertion_parameters.dart | 3 +- .../avoid_non_null_assertion_visitor.dart | 10 +---- .../avoid_non_null_assertion_rule_test.dart | 45 +++++++++++++++---- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/analysis_options.yaml b/lib/analysis_options.yaml index 16b58eb2..d66195f7 100644 --- a/lib/analysis_options.yaml +++ b/lib/analysis_options.yaml @@ -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 diff --git a/lib/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart b/lib/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart index 70ba36be..e38278ed 100644 --- a/lib/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart +++ b/lib/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart @@ -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: /// @@ -19,6 +19,7 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart'; /// diagnostics: /// avoid_non_null_assertion: /// ignored_types: +/// - Map /// - IMap /// - BuiltMap /// ``` @@ -29,9 +30,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: @@ -44,7 +47,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']!; /// ``` @@ -63,8 +66,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, ); diff --git a/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart b/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart index e10755e4..8c17ee80 100644 --- a/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart +++ b/lib/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart @@ -11,11 +11,12 @@ class AvoidNonNullAssertionParameters { /// diagnostics: /// avoid_non_null_assertion: /// ignored_types: + /// - Map /// - IMap /// ``` /// /// ```dart - /// IMap map; + /// Map map; /// map['key']!; // OK /// ``` final Set ignoredTypes; diff --git a/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart b/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart index 5e27f29c..8981d35e 100644 --- a/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart +++ b/lib/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart @@ -27,7 +27,7 @@ class AvoidNonNullAssertionVisitor extends SimpleAstVisitor { if (operand is IndexExpression) { final type = operand.target?.staticType; - if (_isMap(type) || _hasIgnoredType(type)) { + if (_hasIgnoredType(type)) { return; } } @@ -35,14 +35,6 @@ class AvoidNonNullAssertionVisitor extends SimpleAstVisitor { 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; diff --git a/test/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule_test.dart b/test/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule_test.dart index 68997f8c..39e51b43 100644 --- a/test/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule_test.dart +++ b/test/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule_test.dart @@ -21,6 +21,7 @@ plugins: diagnostics: avoid_non_null_assertion: ignored_types: + - Map - IMap - BuiltMap '''; @@ -57,6 +58,32 @@ void m(Object? object) { '''); } + Future 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 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 test_does_not_report_map_access() async { await assertNoDiagnostics(r''' void m() { @@ -66,6 +93,15 @@ void m() { '''); } + Future test_does_not_report_parenthesized_map_access() async { + await assertNoDiagnostics(r''' +void m() { + final map = {'key': 'value'}; + (map['key'])!; +} +'''); + } + Future test_does_not_report_safe_null_check() async { await assertNoDiagnostics(r''' void m(int? number) { @@ -88,15 +124,6 @@ void m(IMap map) { '''); } - Future test_does_not_report_parenthesized_map_access() async { - await assertNoDiagnostics(r''' -void m() { - final map = {'key': 'value'}; - (map['key'])!; -} -'''); - } - Future test_does_not_report_imap_access_with_single_string() async { newAnalysisOptionsYamlFile( testPackageRootPath,