| // Copyright (c) 2017, 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 'package:analyzer/dart/element/element.dart'; |
| import 'package:linter/src/analyzer.dart'; |
| import 'package:linter/src/util/dart_type_utilities.dart'; |
| |
| const _desc = r"Don't check for null in custom == operators."; |
| |
| const _details = r''' |
| |
| **DON'T** check for null in custom == operators. |
| |
| As null is a special type, no class can be equivalent to it. Thus, it is |
| redundant to check whether the other instance is null. |
| |
| **BAD:** |
| ``` |
| class Person { |
| final String name; |
| |
| @override |
| operator ==(other) => |
| other != null && other is Person && name == other.name; |
| } |
| ``` |
| |
| **GOOD:** |
| ``` |
| class Person { |
| final String name; |
| |
| @override |
| operator ==(other) => other is Person && name == other.name; |
| } |
| ``` |
| |
| '''; |
| |
| bool _isComparingEquality(TokenType tokenType) => |
| tokenType == TokenType.BANG_EQ || tokenType == TokenType.EQ_EQ; |
| |
| bool _isComparingParameterWithNull(BinaryExpression node, Element parameter) => |
| _isComparingEquality(node.operator.type) && |
| ((DartTypeUtilities.isNullLiteral(node.leftOperand) && |
| _isParameter(node.rightOperand, parameter)) || |
| (DartTypeUtilities.isNullLiteral(node.rightOperand) && |
| _isParameter(node.leftOperand, parameter))); |
| |
| bool _isParameter(Expression expression, Element parameter) => |
| DartTypeUtilities.getCanonicalElementFromIdentifier(expression) == |
| parameter; |
| |
| bool _isParameterWithQuestion(AstNode node, Element parameter) => |
| (node is PropertyAccess && |
| node.operator?.type == TokenType.QUESTION_PERIOD && |
| DartTypeUtilities.getCanonicalElementFromIdentifier(node.target) == |
| parameter) || |
| (node is MethodInvocation && |
| node.operator?.type == TokenType.QUESTION_PERIOD && |
| DartTypeUtilities.getCanonicalElementFromIdentifier(node.target) == |
| parameter); |
| |
| bool _isParameterWithQuestionQuestion( |
| BinaryExpression node, Element parameter) => |
| node.operator.type == TokenType.QUESTION_QUESTION && |
| _isParameter(node.leftOperand, parameter); |
| |
| class AvoidNullChecksInEqualityOperators extends LintRule |
| implements NodeLintRule { |
| AvoidNullChecksInEqualityOperators() |
| : super( |
| name: 'avoid_null_checks_in_equality_operators', |
| description: _desc, |
| details: _details, |
| group: Group.style); |
| |
| @override |
| void registerNodeProcessors(NodeLintRegistry registry, |
| [LinterContext context]) { |
| final visitor = new _Visitor(this); |
| registry.addMethodDeclaration(this, visitor); |
| } |
| } |
| |
| class _Visitor extends SimpleAstVisitor<void> { |
| final LintRule rule; |
| |
| _Visitor(this.rule); |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| final parameters = node.parameters?.parameters; |
| if (node.name.token?.type == TokenType.EQ_EQ && parameters?.length == 1) { |
| final parameter = DartTypeUtilities.getCanonicalElementFromIdentifier( |
| parameters.first.identifier); |
| bool checkIfParameterIsNull(AstNode node) => |
| _isParameterWithQuestion(node, parameter) || |
| (node is BinaryExpression && |
| (_isParameterWithQuestionQuestion(node, parameter) || |
| _isComparingParameterWithNull(node, parameter))); |
| |
| DartTypeUtilities.traverseNodesInDFS(node.body) |
| .where(checkIfParameterIsNull) |
| .forEach(rule.reportLint); |
| } |
| } |
| } |