blob: 0fe0216f3029f17951c5b21eede893b9249917b3 [file] [log] [blame]
// Copyright (c) 2016, 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';
import '../util/dart_type_utilities.dart';
const _desc =
r"Don't reassign references to parameters of functions or methods.";
const _details = r'''
**DON'T** assign new values to parameters of methods or functions.
Assigning new values to parameters is generally a bad practice unless an
operator such as `??=` is used. Otherwise, arbitrarily reassigning parameters
is usually a mistake.
**BAD:**
```
void badFunction(int parameter) { // LINT
parameter = 4;
}
```
**BAD:**
```
void badFunction(int required, {int optional: 42}) { // LINT
optional ??= 8;
}
```
**BAD:**
```
void badFunctionPositional(int required, [int optional = 42]) { // LINT
optional ??= 8;
}
```
**BAD:**
```
class A {
void badMethod(int parameter) { // LINT
parameter = 4;
}
}
```
**GOOD:**
```
void ok(String parameter) {
print(parameter);
}
```
**GOOD:**
```
void actuallyGood(int required, {int optional}) { // OK
optional ??= ...;
}
```
**GOOD:**
```
void actuallyGoodPositional(int required, [int optional]) { // OK
optional ??= ...;
}
```
**GOOD:**
```
class A {
void ok(String parameter) {
print(parameter);
}
}
```
''';
bool _isDefaultFormalParameterWithDefaultValue(FormalParameter parameter) =>
parameter is DefaultFormalParameter && parameter.defaultValue != null;
bool _isDefaultFormalParameterWithoutDefaultValueReassigned(
FormalParameter parameter, AssignmentExpression assignment) =>
parameter is DefaultFormalParameter &&
parameter.defaultValue == null &&
_isFormalParameterReassigned(parameter, assignment);
bool _isFormalParameterReassigned(
FormalParameter parameter, AssignmentExpression assignment) =>
assignment.leftHandSide is SimpleIdentifier &&
(assignment.leftHandSide as SimpleIdentifier).staticElement ==
parameter.declaredElement;
bool _preOrPostFixExpressionMutation(FormalParameter parameter, AstNode n) =>
n is PrefixExpression &&
n.operand is SimpleIdentifier &&
(n.operand as SimpleIdentifier).staticElement ==
parameter.declaredElement ||
n is PostfixExpression &&
n.operand is SimpleIdentifier &&
(n.operand as SimpleIdentifier).staticElement ==
parameter.declaredElement;
class ParameterAssignments extends LintRule implements NodeLintRule {
ParameterAssignments()
: super(
name: 'parameter_assignments',
description: _desc,
details: _details,
group: Group.style);
@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
final visitor = _Visitor(this);
registry.addFunctionDeclaration(this, visitor);
registry.addMethodDeclaration(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
_Visitor(this.rule);
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
final parameters = node.functionExpression.parameters;
if (parameters != null) {
// Getter do not have formal parameters.
parameters.parameters.forEach((e) {
if (node.functionExpression.body
.isPotentiallyMutatedInScope(e.declaredElement)) {
_reportIfSimpleParameterOrWithDefaultValue(e, node);
}
});
}
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
final parameterList = node?.parameters;
if (parameterList != null) {
// Getters don't have parameters.
parameterList.parameters.forEach((e) {
if (node.body.isPotentiallyMutatedInScope(e.declaredElement)) {
_reportIfSimpleParameterOrWithDefaultValue(e, node);
}
});
}
}
void _reportIfSimpleParameterOrWithDefaultValue(
FormalParameter parameter, AstNode functionOrMethodDeclaration) {
final nodes =
DartTypeUtilities.traverseNodesInDFS(functionOrMethodDeclaration);
if (parameter is SimpleFormalParameter ||
_isDefaultFormalParameterWithDefaultValue(parameter)) {
final mutatedNodes = nodes.where((n) =>
(n is AssignmentExpression &&
_isFormalParameterReassigned(parameter, n)) ||
_preOrPostFixExpressionMutation(parameter, n));
mutatedNodes.forEach(rule.reportLint);
return;
}
final assignmentsNodes = nodes
.where((n) =>
n is AssignmentExpression &&
_isDefaultFormalParameterWithoutDefaultValueReassigned(
parameter, n))
.toList();
final nonNullCoalescingAssignments = assignmentsNodes.where((n) =>
(n as AssignmentExpression).operator.type !=
TokenType.QUESTION_QUESTION_EQ);
if (assignmentsNodes.length > 1 ||
nonNullCoalescingAssignments.isNotEmpty) {
final node = assignmentsNodes.length > 1
? assignmentsNodes.last
: nonNullCoalescingAssignments.isNotEmpty
? nonNullCoalescingAssignments.first
: parameter;
rule.reportLint(node);
}
}
}