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
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:analysis_server_plugin/plugin.dart';
import 'package:analysis_server_plugin/registry.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';

/// The entry point for the Solid Lints analyser server plugin.
///
Expand All @@ -21,5 +22,8 @@ class SolidLintsPlugin extends Plugin {
registry.registerLintRule(
AvoidGlobalStateRule(),
);
registry.registerLintRule(
AvoidNonNullAssertionRule(),
);
}
}
4 changes: 2 additions & 2 deletions lib/src/lints/avoid_global_state/avoid_global_state_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class AvoidGlobalStateRule extends AnalysisRule {
static const String lintName = 'avoid_global_state';

/// Lint code used for suppression and reporting.
static const LintCode code = LintCode(
static const LintCode _code = LintCode(
lintName,
'Avoid variables that can be globally mutated.',
correctionMessage:
Expand All @@ -54,7 +54,7 @@ class AvoidGlobalStateRule extends AnalysisRule {
);

@override
LintCode get diagnosticCode => code;
LintCode get diagnosticCode => _code;

@override
void registerNodeProcessors(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
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/visitors/avoid_non_null_assertion_visitor.dart';

/// Rule which warns about usages of bang operator ("!")
/// as it may result in unexpected runtime exceptions.
Expand Down Expand Up @@ -37,56 +35,32 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// final map = {'key': 'value'};
/// map['key']!;
/// ```
class AvoidNonNullAssertionRule extends SolidLintRule {
/// This lint rule represents
/// the error whether we use bang operator.
static const lintName = 'avoid_non_null_assertion';
class AvoidNonNullAssertionRule extends AnalysisRule {
/// Name of the lint
static const String lintName = 'avoid_non_null_assertion';

AvoidNonNullAssertionRule._(super.config);
/// Lint code used for suppression and reporting
static const LintCode _code = LintCode(
lintName,
'Avoid using the bang operator. It may result in runtime exceptions.',
);

/// Creates a new instance of [AvoidNonNullAssertionRule]
/// based on the lint configuration.
factory AvoidNonNullAssertionRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => 'Avoid using the bang operator. '
'It may result in runtime exceptions.',
);
/// creates an instance of [AvoidNonNullAssertionRule]
AvoidNonNullAssertionRule()
: super(
name: lintName,
description:
'Warns about usages of bang operator (!) except valid Map access.',
);

return AvoidNonNullAssertionRule._(rule);
}
@override
LintCode get diagnosticCode => _code;

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addPostfixExpression((node) {
if (node.operator.type != TokenType.BANG) {
return;
}

// DCM's and Flutter's documentation treats "bang" as a valid way of
// accessing a Map. For compatibility it's excluded from this rule.
// See more:
// * https://dcm.dev/docs/rules/common/avoid-non-null-assertion
// * https://dart.dev/null-safety/understanding-null-safety#the-map-index-operator-is-nullable
final operand = node.operand;
if (operand is IndexExpression) {
final type = operand.target?.staticType;
final isInterface = type is InterfaceType;
final isMap = isInterface &&
(type.isDartCoreMap ||
type.allSupertypes.any((v) => v.isDartCoreMap));

if (isMap) {
return;
}
}

reporter.atNode(node, code);
});
registry.addPostfixExpression(this, AvoidNonNullAssertionVisitor(this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:analyzer/dart/ast/ast.dart';
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';

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

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

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

final operand = node.operand;

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

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

rule.reportAtNode(node);
}

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

return type.isDartCoreMap || type.allSupertypes.any((v) => v.isDartCoreMap);
}
}
25 changes: 0 additions & 25 deletions lint_test/avoid_non_null_assertion_test.dart

This file was deleted.

59 changes: 59 additions & 0 deletions test/avoid_non_null_assertion_rule_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:analyzer_testing/analysis_rule/analysis_rule.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';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AvoidNonNullAssertionRuleTest);
});
}

@reflectiveTest
class AvoidNonNullAssertionRuleTest extends AnalysisRuleTest {
@override
void setUp() {
rule = AvoidNonNullAssertionRule();
super.setUp();
}

void test_reports_non_null_assertion_on_nullable_value() async {
await assertDiagnostics(
r'''
void m(int? number) {
final value = number!;
}
''',
[lint(38, 7)],
Comment thread
Islam-Shaaban-Ibrahim marked this conversation as resolved.
);
}

void test_reports_non_null_assertion_on_method_call() async {
await assertDiagnostics(
r'''
void m(Object? object) {
object!.toString();
}
''',
[lint(27, 7)],
);
}

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

void test_does_not_report_safe_null_check() async {
await assertNoDiagnostics(r'''
void m(int? number) {
if (number != null) {
final value = number;
}
}
''');
}
}
Loading