blob: bb4f7d68c9a202f5965870ecb90684fb28bed282 [file] [log] [blame]
// 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);
}
}
}
}