This repository has been archived by the owner on Jul 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 258
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
436 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Avoid unused parameters | ||
|
||
## Rule id | ||
|
||
avoid-unused-parameters | ||
|
||
## Description | ||
|
||
Checks for unused parameters inside a function or method body. | ||
For overridden methods suggests renaming unused parameters to \_, \_\_, etc. | ||
|
||
Note: abstract classes are completely ignored by the rule to avoid redundant checks for potentially overridden methods. | ||
|
||
### Example | ||
|
||
Bad: | ||
|
||
```dart | ||
void someFunction(String s) { | ||
return; | ||
} | ||
class SomeClass { | ||
void method(String s) { | ||
return; | ||
} | ||
} | ||
``` | ||
|
||
Good: | ||
|
||
```dart | ||
void someFunction() { | ||
return; | ||
} | ||
class SomeClass { | ||
void method() { | ||
return; | ||
} | ||
} | ||
void someFunction(String s) { | ||
print(s); | ||
return; | ||
} | ||
class SomeClass { | ||
void method(String s) { | ||
print(s); | ||
return; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/syntactic_entity.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
|
||
import '../models/code_issue.dart'; | ||
import '../models/code_issue_severity.dart'; | ||
import '../models/source.dart'; | ||
import 'base_rule.dart'; | ||
import 'rule_utils.dart'; | ||
|
||
// Inspired by PVS-Studio (https://www.viva64.com/en/w/v6022/) | ||
|
||
class AvoidUnusedParameters extends BaseRule { | ||
static const String ruleId = 'avoid-unused-parameters'; | ||
static const _documentationUrl = 'https://git.io/JL153'; | ||
|
||
static const _warningMessage = 'Parameter is unused'; | ||
static const _renameMessage = | ||
'Parameter is unused, consider renaming it to _, __, etc.'; | ||
|
||
AvoidUnusedParameters({ | ||
Map<String, Object> config = const {}, | ||
}) : super( | ||
id: ruleId, | ||
documentation: Uri.parse(_documentationUrl), | ||
severity: CodeIssueSeverity.fromJson(config['severity'] as String) ?? | ||
CodeIssueSeverity.warning, | ||
); | ||
|
||
@override | ||
Iterable<CodeIssue> check(Source source) { | ||
final _visitor = _Visitor(); | ||
|
||
source.compilationUnit.visitChildren(_visitor); | ||
|
||
return [ | ||
..._visitor.unusedParameters | ||
.map((parameter) => createIssue( | ||
this, | ||
_warningMessage, | ||
null, | ||
null, | ||
source.url, | ||
source.content, | ||
source.compilationUnit.lineInfo, | ||
parameter, | ||
)) | ||
.toList(growable: false), | ||
..._visitor.renameSuggestions | ||
.map((parameter) => createIssue( | ||
this, | ||
_renameMessage, | ||
null, | ||
null, | ||
source.url, | ||
source.content, | ||
source.compilationUnit.lineInfo, | ||
parameter, | ||
)) | ||
.toList(), | ||
]; | ||
} | ||
} | ||
|
||
class _Visitor extends RecursiveAstVisitor<void> { | ||
final _unusedParameters = <FormalParameter>[]; | ||
final _renameSuggestions = <FormalParameter>[]; | ||
|
||
Iterable<FormalParameter> get unusedParameters => _unusedParameters; | ||
|
||
Iterable<FormalParameter> get renameSuggestions => _renameSuggestions; | ||
|
||
@override | ||
void visitMethodDeclaration(MethodDeclaration node) { | ||
super.visitMethodDeclaration(node); | ||
|
||
final parent = node.parent; | ||
|
||
if (parent is ClassDeclaration && parent.isAbstract) { | ||
return; | ||
} | ||
|
||
final isOverride = node.metadata.firstWhere( | ||
(node) => node.name.name == 'override' && node.atSign.type.lexeme == '@', | ||
orElse: () => null, | ||
); | ||
|
||
if (isOverride != null) { | ||
_renameSuggestions.addAll( | ||
_checkParameters( | ||
node.body.childEntities, | ||
node.parameters.parameters, | ||
).where( | ||
(parameter) => | ||
parameter.identifier.name.replaceAll('_', '').isNotEmpty, | ||
), | ||
); | ||
} else { | ||
_unusedParameters.addAll( | ||
_checkParameters( | ||
node.body.childEntities, | ||
node.parameters.parameters, | ||
), | ||
); | ||
} | ||
} | ||
|
||
@override | ||
void visitFunctionDeclaration(FunctionDeclaration node) { | ||
super.visitFunctionDeclaration(node); | ||
|
||
_unusedParameters.addAll( | ||
_checkParameters( | ||
node.functionExpression.body.childEntities, | ||
node.functionExpression.parameters.parameters, | ||
), | ||
); | ||
} | ||
|
||
Iterable<FormalParameter> _checkParameters( | ||
Iterable<SyntacticEntity> children, | ||
NodeList<FormalParameter> parameters, | ||
) { | ||
final result = <FormalParameter>[]; | ||
|
||
final usages = _getAllUsages(children, []) | ||
.map((identifier) => identifier.name) | ||
.toList(); | ||
|
||
for (final parameter in parameters) { | ||
if (!usages.contains(parameter.identifier.name)) { | ||
result.add(parameter); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
Iterable<Identifier> _getAllUsages( | ||
Iterable<SyntacticEntity> children, | ||
Iterable<String> ignoredNames, | ||
) { | ||
final usages = <Identifier>[]; | ||
final ignored = [...ignoredNames]; | ||
|
||
for (final child in children) { | ||
if (child is FunctionExpression) { | ||
for (final parameter in child.parameters.parameters) { | ||
ignored.add(parameter.identifier.name); | ||
} | ||
} else if (child is Identifier && | ||
!ignored.contains(child.name) && | ||
child.parent is! PropertyAccess) { | ||
usages.add(child); | ||
} | ||
|
||
if (child is AstNode) { | ||
usages.addAll(_getAllUsages(child.childEntities, ignored)); | ||
} | ||
} | ||
|
||
return usages; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
test/rules/avoid_unused_parameters/avoid_unused_parameters_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import 'dart:io'; | ||
|
||
import 'package:analyzer/dart/analysis/utilities.dart'; | ||
import 'package:dart_code_metrics/src/models/code_issue_severity.dart'; | ||
import 'package:dart_code_metrics/src/models/source.dart'; | ||
import 'package:dart_code_metrics/src/rules/avoid_unused_parameters.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
const correctExamplePath = | ||
'test/rules/avoid_unused_parameters/examples/correct_example.dart'; | ||
const incorrectExamplePath = | ||
'test/rules/avoid_unused_parameters/examples/incorrect_example.dart'; | ||
|
||
void main() { | ||
group('AvoidUnusedParameters', () { | ||
test('initialization', () async { | ||
final path = File(correctExamplePath).absolute.path; | ||
final sourceUrl = Uri.parse(path); | ||
|
||
final parseResult = await resolveFile(path: path); | ||
|
||
final issues = AvoidUnusedParameters().check(Source( | ||
sourceUrl, | ||
parseResult.content, | ||
parseResult.unit, | ||
)); | ||
|
||
expect( | ||
issues.every((issue) => issue.ruleId == 'avoid-unused-parameters'), | ||
isTrue, | ||
); | ||
expect( | ||
issues.every((issue) => issue.severity == CodeIssueSeverity.warning), | ||
isTrue, | ||
); | ||
}); | ||
|
||
test('reports no issues', () async { | ||
final path = File(correctExamplePath).absolute.path; | ||
final sourceUrl = Uri.parse(path); | ||
|
||
final parseResult = await resolveFile(path: path); | ||
|
||
final issues = AvoidUnusedParameters().check(Source( | ||
sourceUrl, | ||
parseResult.content, | ||
parseResult.unit, | ||
)); | ||
|
||
expect(issues.isEmpty, isTrue); | ||
}); | ||
|
||
test('reports about found issues', () async { | ||
final path = File(incorrectExamplePath).absolute.path; | ||
final sourceUrl = Uri.parse(path); | ||
|
||
final parseResult = await resolveFile(path: path); | ||
|
||
final issues = AvoidUnusedParameters().check(Source( | ||
sourceUrl, | ||
parseResult.content, | ||
parseResult.unit, | ||
)); | ||
|
||
expect( | ||
issues.map((issue) => issue.sourceSpan.start.offset), | ||
equals([132, 190, 341, 379, 607, 493, 677, 94]), | ||
); | ||
expect( | ||
issues.map((issue) => issue.sourceSpan.start.line), | ||
equals([7, 9, 18, 20, 29, 24, 34, 5]), | ||
); | ||
expect( | ||
issues.map((issue) => issue.sourceSpan.start.column), | ||
equals([20, 40, 20, 20, 28, 38, 20, 23]), | ||
); | ||
expect( | ||
issues.map((issue) => issue.sourceSpan.end.offset), | ||
equals([145, 209, 354, 397, 623, 506, 693, 107]), | ||
); | ||
expect( | ||
issues.map((issue) => issue.sourceSpan.text), | ||
equals([ | ||
'String string', | ||
'String secondString', | ||
'String string', | ||
'String firstString', | ||
'TestClass object', | ||
'String string', | ||
'TestClass object', | ||
'String string', | ||
]), | ||
); | ||
expect( | ||
issues.map((issue) => issue.message), | ||
equals([ | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused', | ||
'Parameter is unused, consider renaming it to _, __, etc.', | ||
]), | ||
); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.