Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.
Closed
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
1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ linter:
- no_logic_in_create_state
- no_runtimeType_toString
- non_constant_identifier_names
- non_nullable_equals_parameter
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import 'rules/no_leading_underscores_for_local_identifiers.dart';
import 'rules/no_logic_in_create_state.dart';
import 'rules/no_runtimeType_toString.dart';
import 'rules/non_constant_identifier_names.dart';
import 'rules/non_nullable_equals_parameter.dart';
import 'rules/noop_primitive_operations.dart';
import 'rules/null_check_on_nullable_type_parameter.dart';
import 'rules/null_closures.dart';
Expand Down Expand Up @@ -322,6 +323,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(NoDefaultCases())
..register(NoDuplicateCaseValues())
..register(NonConstantIdentifierNames())
..register(NonNullableEqualsParameter())
..register(NoLeadingUnderscoresForLibraryPrefixes())
..register(NoLeadingUnderscoresForLocalIdentifiers())
..register(NoLogicInCreateState())
Expand Down
104 changes: 104 additions & 0 deletions lib/src/rules/non_nullable_equals_parameter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../analyzer.dart';

const _desc = r'The parameter type of `==` operators should be non-nullable.';

const _details = r'''
The parameter type of `==` operators should be non-nullable.

`null` is never passed to `operator ==`, so the parameter type of an `==`
operator override should always be non-nullable.

**BAD:**
```dart
class C {
String key;
B(this.key);
@override
operator ==(Object? other) => other is C && other.key == key;
}
```

**GOOD:**
```dart
class C {
String key;
B(this.key);
@override
operator ==(Object other) => other is C && other.key == key;
}
```
''';

class NonNullableEqualsParameter extends LintRule {
static const LintCode code = LintCode('non_nullable_equals_parameter',
'The parameter should have a non-nullable type.',
correctionMessage: 'Try using a non-nullable type.');

NonNullableEqualsParameter()
: super(
name: 'non_nullable_equals_parameter',
description: _desc,
details: _details,
group: Group.style);

@override
LintCode get lintCode => code;

@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this, context);
registry.addMethodDeclaration(this, visitor);
}
}

class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;

final LinterContext context;

_Visitor(this.rule, this.context);

@override
void visitMethodDeclaration(MethodDeclaration node) {
if (node.name.type != TokenType.EQ_EQ) {
return;
}

var parameters = node.parameters;
if (parameters == null) {
return;
}

if (parameters.parameters.length != 1) {
return;
}

var parameter = parameters.parameters.first;
var parameterElement = parameter.declaredElement;

if (parameterElement == null) {
return;
}

var type = parameterElement.type;

if (!type.isDartCoreObject && !type.isDynamic) {
// There is no legal way to define a nullable parameter type, which is not
// `dynamic` or `Object?`.
return;
}

if (context.typeSystem.isNullable(parameterElement.type)) {
rule.reportLint(parameter);
}
}
}
3 changes: 3 additions & 0 deletions test/rules/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import 'missing_whitespace_between_adjacent_strings_test.dart'
as missing_whitespace_between_adjacent_strings;
import 'non_constant_identifier_names_test.dart'
as non_constant_identifier_names;
import 'non_nullable_equals_parameter_test.dart'
as non_nullable_equals_parameter;
import 'null_closures_test.dart' as null_closures;
import 'omit_local_variable_types_test.dart' as omit_local_variable_types;
import 'overridden_fields_test.dart' as overridden_fields;
Expand Down Expand Up @@ -130,6 +132,7 @@ void main() {
literal_only_boolean_expressions.main();
missing_whitespace_between_adjacent_strings.main();
non_constant_identifier_names.main();
non_nullable_equals_parameter.main();
null_closures.main();
omit_local_variable_types.main();
overridden_fields.main();
Expand Down
101 changes: 101 additions & 0 deletions test/rules/non_nullable_equals_parameter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 '../rule_test_support.dart';

main() {
defineReflectiveSuite(() {
defineReflectiveTests(NonNullableEqualsParameterTest);
});
}

@reflectiveTest
class NonNullableEqualsParameterTest extends LintRuleTest {
@override
String get lintRule => 'non_nullable_equals_parameter';

test_class_nonNullableParameter() async {
await assertNoDiagnostics(r'''
class C {
bool operator ==(Object other) => other is C;
}
''');
}

test_class_nonNullableParameter_inherited() async {
await assertNoDiagnostics(r'''
class C {
bool operator ==(Object other) => other is C;
}
class D extends C {
bool operator ==(other) => other is D;
}
''');
}

test_class_nonNullableParameter_narrowedType() async {
await assertNoDiagnostics(r'''
class C {
bool operator ==(covariant int other) => other is C;
}
''');
}

test_class_nullableParameter() async {
await assertDiagnostics(r'''
class C {
bool operator ==(Object? other) => other is C;
}
''', [
lint(29, 13),
]);
}

test_class_nullableParameter_dynamic() async {
await assertDiagnostics(r'''
class C {
bool operator ==(dynamic other) => other is C;
}
''', [
lint(29, 13),
]);
}

test_class_nullableParameter_dynamicAndInherited() async {
await assertDiagnostics(r'''
class C {
bool operator ==(dynamic other) => other is C;
}
class D extends C {
bool operator ==(other) => other is D;
}
''', [
lint(29, 13),
lint(100, 5),
]);
}

test_class_nullableParameter_nonObject() async {
await assertDiagnostics(r'''
class C {
bool operator ==(num? other) => false;
}
''', [
error(CompileTimeErrorCode.INVALID_OVERRIDE, 26, 2),
// No lint.
]);
}

test_mixin_nullableParameter() async {
await assertDiagnostics(r'''
mixin M {
bool operator ==(Object? other) => other is M;
}
''', [
lint(29, 13),
]);
}
}