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: 4 additions & 1 deletion lib/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ solid_lints:
allow_initialized: true
ignored_types:
- AnimationController
avoid_non_null_assertion: true
avoid_non_null_assertion:
ignored_types:
- IMap
- BuiltMap
avoid_returning_widgets: true
avoid_unnecessary_return_variable: true
avoid_unnecessary_setstate: true
Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class SolidLintsPlugin extends Plugin {
final lintRules = [
AvoidFinalWithGetterRule(),
AvoidGlobalStateRule(),
AvoidNonNullAssertionRule(),
AvoidNonNullAssertionRule(analysisOptionsLoader: analysisLoader),
avoidUnnecessaryTypeAssertionsRule,
AvoidDebugPrintInReleaseRule(),
doubleLiteralFormatRule,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/visitors/avoid_non_null_assertion_visitor.dart';
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.
Expand Down Expand Up @@ -35,7 +36,8 @@ import 'package:solid_lints/src/lints/avoid_non_null_assertion/visitors/avoid_no
/// final map = {'key': 'value'};
/// map['key']!;
/// ```
class AvoidNonNullAssertionRule extends AnalysisRule {
class AvoidNonNullAssertionRule
extends SolidLintRule<AvoidNonNullAssertionParameters> {
/// Name of the lint
static const String lintName = 'avoid_non_null_assertion';

Expand All @@ -46,11 +48,12 @@ class AvoidNonNullAssertionRule extends AnalysisRule {
);

/// creates an instance of [AvoidNonNullAssertionRule]
AvoidNonNullAssertionRule()
: super(
AvoidNonNullAssertionRule({required super.analysisOptionsLoader})
: super.withParameters(
name: lintName,
description:
'Warns about usages of bang operator (!) except valid Map access.',
parametersParser: AvoidNonNullAssertionParameters.fromJson,
);

@override
Expand All @@ -61,6 +64,13 @@ class AvoidNonNullAssertionRule extends AnalysisRule {
RuleVisitorRegistry registry,
RuleContext context,
) {
registry.addPostfixExpression(this, AvoidNonNullAssertionVisitor(this));
final parameters =
getParametersForContext(context) ??
AvoidNonNullAssertionParameters.empty();

registry.addPostfixExpression(
this,
AvoidNonNullAssertionVisitor(this, parameters),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// A data model class that represents the "avoid non null assertion" input
/// parameters.
class AvoidNonNullAssertionParameters {
/// Types that would be ignored by avoid-non-null-assertion rule
///
/// Example:
///
/// ```yaml
/// plugins:
/// solid_lints:
/// diagnostics:
/// avoid_non_null_assertion:
/// ignored_types:
/// - IMap
/// ```
///
/// ```dart
/// IMap<String, String> map;
/// map['key']!; // OK
/// ```
final Set<String> ignoredTypes;

/// Constructor for [AvoidNonNullAssertionParameters] model
const AvoidNonNullAssertionParameters({
required this.ignoredTypes,
});

/// Empty [AvoidNonNullAssertionParameters] model, ignores nothing.
factory AvoidNonNullAssertionParameters.empty() =>
const AvoidNonNullAssertionParameters(
ignoredTypes: {},
);

/// Method for creating from json data
factory AvoidNonNullAssertionParameters.fromJson(Map<String, Object?> json) {
final raw = json['ignored_types'];
final excludeList = switch (raw) {
final Iterable<Object?> rawList => rawList.whereType<String>().toSet(),
final Map<Object?, Object?> rawMap =>
rawMap.keys.whereType<String>().toSet(),
final String rawString => {rawString},
_ => const <String>{},
};
Comment thread
solid-illiaaihistov marked this conversation as resolved.

return AvoidNonNullAssertionParameters(
ignoredTypes: excludeList,
);
}
Comment thread
solid-illiaaihistov marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@ import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/models/avoid_non_null_assertion_parameters.dart';
import 'package:solid_lints/src/utils/types_utils.dart';

/// visitor for [AvoidNonNullAssertionRule]
class AvoidNonNullAssertionVisitor extends SimpleAstVisitor<void> {
/// Rule associated with this visitor
final AvoidNonNullAssertionRule rule;

final AvoidNonNullAssertionParameters _parameters;

/// Creates an instance of [AvoidNonNullAssertionVisitor]
AvoidNonNullAssertionVisitor(this.rule);
AvoidNonNullAssertionVisitor(this.rule, this._parameters);

@override
void visitPostfixExpression(PostfixExpression node) {
if (node.operator.type != TokenType.BANG) {
return;
}

final operand = node.operand;
final operand = node.operand.unParenthesized;

if (operand is IndexExpression) {
final type = operand.target?.staticType;

if (_isMap(type)) {
if (_isMap(type) || _hasIgnoredType(type)) {
return;
}
}
Expand All @@ -38,4 +42,14 @@ class AvoidNonNullAssertionVisitor extends SimpleAstVisitor<void> {

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

bool _hasIgnoredType(DartType? type) {
if (type == null) {
return false;
}

return type.hasIgnoredType(
ignoredTypes: _parameters.ignoredTypes,
);
Comment thread
solid-illiaaihistov marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:analyzer_testing/analysis_rule/analysis_rule.dart';
import 'package:analyzer_testing/utilities/utilities.dart';
import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

Expand All @@ -13,10 +15,30 @@ void main() {
@reflectiveTest
class AvoidNonNullAssertionRuleTest extends AnalysisRuleTest
with AutoTestLintOffsets {
static const _mockAnalysisOptionsContent = '''
plugins:
solid_lints:
diagnostics:
avoid_non_null_assertion:
ignored_types:
- IMap
- BuiltMap
''';

@override
void setUp() {
rule = AvoidNonNullAssertionRule();
rule = AvoidNonNullAssertionRule(
analysisOptionsLoader: AnalysisOptionsLoader(
resourceProvider: resourceProvider,
),
);
super.setUp();

newAnalysisOptionsYamlFile(
testPackageRootPath,
'''${analysisOptionsContent(rules: [rule.name])}
$_mockAnalysisOptionsContent''',
);
}

Future<void> test_reports_non_null_assertion_on_nullable_value() async {
Expand Down Expand Up @@ -51,6 +73,48 @@ void m(int? number) {
final value = number;
}
}
''');
}

Future<void> test_does_not_report_imap_access() async {
await assertNoDiagnostics(r'''
class IMap<K, V> {
V? operator [](K key) => null;
}

void m(IMap<String, String> map) {
map['key']!;
}
''');
}

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,
'''${analysisOptionsContent(rules: [rule.name])}
plugins:
solid_lints:
diagnostics:
avoid_non_null_assertion:
ignored_types: IMap''',
);
await assertNoDiagnostics(r'''
class IMap<K, V> {
V? operator [](K key) => null;
}

void m(IMap<String, String> map) {
map['key']!;
}
''');
}
}
Loading