| // 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/analysis_rule/rule_context.dart'; |
| import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| |
| import '../analyzer.dart'; |
| |
| const _desc = r'Avoid using unnecessary statements.'; |
| |
| class UnnecessaryStatements extends LintRule { |
| UnnecessaryStatements() |
| : super(name: LintNames.unnecessary_statements, description: _desc); |
| |
| @override |
| DiagnosticCode get diagnosticCode => LinterLintCode.unnecessaryStatements; |
| |
| @override |
| void registerNodeProcessors( |
| RuleVisitorRegistry registry, |
| RuleContext context, |
| ) { |
| var visitor = _Visitor(_ReportNoClearEffectVisitor(this)); |
| registry.addExpressionStatement(this, visitor); |
| registry.addForStatement(this, visitor); |
| registry.addCascadeExpression(this, visitor); |
| } |
| } |
| |
| /// A visitor that reports expressions that have no clear effect. |
| /// |
| /// This visitor works a little differently from most. It reports lint rule |
| /// violations in `visitNode`, a sort of "catch all" location. It also contains |
| /// many empty-bodied "visit" method overrides that serve to short-circuit a |
| /// traversal down the syntax tree. Each empty-bodied "visit" method represents |
| /// a case where an expression can validly act as a statement, as there are |
| /// common cases where the expression has a clear effect. |
| /// |
| /// In this way the visitor's visitations are typically very shallow, starting |
| /// either with a method that just returns without visiting any children, or |
| /// starting with `visitNode`, which reports a violation and also does not |
| /// descend. We descend into only a few node types, like binary expressions and |
| /// conditional expressions. |
| class _ReportNoClearEffectVisitor extends UnifyingAstVisitor<void> { |
| final LintRule rule; |
| |
| _ReportNoClearEffectVisitor(this.rule); |
| |
| @override |
| void visitAsExpression(AsExpression node) { |
| // https://github.com/dart-lang/linter/issues/2163 |
| } |
| |
| @override |
| void visitAssignmentExpression(AssignmentExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitAwaitExpression(AwaitExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitBinaryExpression(BinaryExpression node) { |
| switch (node.operator.lexeme) { |
| case '??': |
| case '||': |
| case '&&': |
| // These are OK when used for control flow. |
| node.rightOperand.accept(this); |
| return; |
| } |
| |
| super.visitBinaryExpression(node); |
| } |
| |
| @override |
| void visitCascadeExpression(CascadeExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| node.thenExpression.accept(this); |
| node.elseExpression.accept(this); |
| } |
| |
| @override |
| void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| // A few APIs use this for side effects, like Timer. Also, constructors |
| // that have side effects typically have tests will often include an |
| // instantiation expression statement. |
| } |
| |
| @override |
| void visitMethodInvocation(MethodInvocation node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitNode(AstNode expression) { |
| rule.reportAtNode(expression); |
| } |
| |
| @override |
| void visitPatternAssignment(PatternAssignment node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitPostfixExpression(PostfixExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| // Allow getters; getters with side effects were the main cause of false |
| // positives. |
| var element = node.identifier.element; |
| if (element is GetterElement && !element.isSynthetic) { |
| return; |
| } |
| |
| super.visitPrefixedIdentifier(node); |
| } |
| |
| @override |
| void visitPrefixExpression(PrefixExpression node) { |
| if (node.operator.lexeme == '--' || node.operator.lexeme == '++') { |
| // Has a clear effect. Do not descend. |
| return; |
| } |
| super.visitPrefixExpression(node); |
| } |
| |
| @override |
| void visitPropertyAccess(PropertyAccess node) { |
| // Allow getters; previously getters with side effects were the main cause |
| // of false positives. |
| var element = node.propertyName.element; |
| if (element is GetterElement && !element.isSynthetic) { |
| return; |
| } |
| |
| super.visitPropertyAccess(node); |
| } |
| |
| @override |
| void visitRethrowExpression(RethrowExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| // Allow getter (in this case with an implicit `this.`); previously, getters |
| // with side effects were the main cause of false positives. |
| var element = node.element; |
| if (element is GetterElement && !element.isSynthetic) { |
| return; |
| } |
| |
| super.visitSimpleIdentifier(node); |
| } |
| |
| @override |
| void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| // Has a clear effect. Do not descend. |
| } |
| |
| @override |
| void visitThrowExpression(ThrowExpression node) { |
| // Has a clear effect. Do not descend. |
| } |
| } |
| |
| class _Visitor extends SimpleAstVisitor<void> { |
| final _ReportNoClearEffectVisitor reportNoClearEffect; |
| |
| _Visitor(this.reportNoClearEffect); |
| @override |
| void visitCascadeExpression(CascadeExpression node) { |
| for (var section in node.cascadeSections) { |
| if (section is PropertyAccess && section.staticType is FunctionType) { |
| reportNoClearEffect.rule.reportAtNode(section); |
| } |
| } |
| } |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| if (node.parent is FunctionBody) { |
| return; |
| } |
| node.expression.accept(reportNoClearEffect); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| var loopParts = node.forLoopParts; |
| if (loopParts is ForPartsWithExpression) { |
| loopParts.initialization?.accept(reportNoClearEffect); |
| for (var u in loopParts.updaters) { |
| u.accept(reportNoClearEffect); |
| } |
| } |
| } |
| } |