This repository has been archived by the owner on Jul 16, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce new command check-unnecessary-nullable (#874)
* feat: introduce new command check-unnecessary-nullable * chore: update changelog * fix: add support for property access * fix: add support for index expressions * fix: add support for all expressions as arguments * fix: add null literal check * fix: support dynamic type for arguments * chore: downgrade react version * chore: fix comments
- Loading branch information
Showing
34 changed files
with
1,390 additions
and
89 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
74 changes: 74 additions & 0 deletions
74
lib/src/analyzers/unnecessary_nullable_analyzer/declarations_visitor.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,74 @@ | ||
// ignore_for_file: public_member_api_docs | ||
|
||
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/nullability_suffix.dart'; | ||
|
||
import '../../utils/node_utils.dart'; | ||
|
||
typedef DeclarationUsages = Map<Element, Iterable<FormalParameter>>; | ||
|
||
class DeclarationsVisitor extends RecursiveAstVisitor<void> { | ||
final DeclarationUsages declarations = {}; | ||
|
||
@override | ||
void visitMethodDeclaration(MethodDeclaration node) { | ||
super.visitMethodDeclaration(node); | ||
|
||
final parameters = node.parameters?.parameters; | ||
if (parameters == null || !_hasNullableParameters(parameters)) { | ||
return; | ||
} | ||
|
||
_getDeclarationElement(node, parameters); | ||
} | ||
|
||
@override | ||
void visitFunctionDeclaration(FunctionDeclaration node) { | ||
super.visitFunctionDeclaration(node); | ||
|
||
final parameters = node.functionExpression.parameters?.parameters; | ||
if (isEntrypoint(node.name.name, node.metadata) || | ||
(parameters == null || !_hasNullableParameters(parameters))) { | ||
return; | ||
} | ||
|
||
_getDeclarationElement(node, parameters); | ||
} | ||
|
||
@override | ||
void visitConstructorDeclaration(ConstructorDeclaration node) { | ||
super.visitConstructorDeclaration(node); | ||
|
||
final parameters = node.parameters.parameters; | ||
if (!_hasNullableParameters(parameters)) { | ||
return; | ||
} | ||
|
||
_getDeclarationElement(node, parameters); | ||
} | ||
|
||
bool _hasNullableParameters(Iterable<FormalParameter> parameters) => | ||
parameters.any((parameter) { | ||
final type = parameter.declaredElement?.type; | ||
|
||
return type != null && | ||
(type.nullabilitySuffix == NullabilitySuffix.question && | ||
(!parameter.isOptional || | ||
parameter.isOptional && parameter.isRequired)) || | ||
(parameter is DefaultFormalParameter && | ||
parameter.defaultValue == null); | ||
}); | ||
|
||
void _getDeclarationElement( | ||
Declaration node, | ||
Iterable<FormalParameter> parameters, | ||
) { | ||
final element = node.declaredElement; | ||
|
||
if (element != null) { | ||
declarations[element] = parameters; | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
lib/src/analyzers/unnecessary_nullable_analyzer/invocations_visitor.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,56 @@ | ||
// ignore_for_file: public_member_api_docs | ||
|
||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
import 'package:analyzer/dart/element/element.dart'; | ||
|
||
import 'models/invocations_usage.dart'; | ||
|
||
class InvocationsVisitor extends RecursiveAstVisitor<void> { | ||
final invocationsUsages = InvocationsUsage(); | ||
|
||
@override | ||
void visitExportDirective(ExportDirective node) { | ||
super.visitExportDirective(node); | ||
|
||
final path = node.uriSource?.fullName; | ||
if (path != null) { | ||
invocationsUsages.exports.add(path); | ||
} | ||
} | ||
|
||
@override | ||
void visitMethodInvocation(MethodInvocation node) { | ||
super.visitMethodInvocation(node); | ||
|
||
_recordUsedElement(node.methodName.staticElement, node.argumentList); | ||
} | ||
|
||
@override | ||
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | ||
super.visitFunctionExpressionInvocation(node); | ||
|
||
_recordUsedElement(node.staticElement, node.argumentList); | ||
} | ||
|
||
@override | ||
void visitInstanceCreationExpression(InstanceCreationExpression node) { | ||
super.visitInstanceCreationExpression(node); | ||
|
||
_recordUsedElement(node.constructorName.staticElement, node.argumentList); | ||
} | ||
|
||
/// Records use of a not prefixed [element]. | ||
void _recordUsedElement(Element? element, ArgumentList arguments) { | ||
if (element == null) { | ||
return; | ||
} | ||
// Ignore if an unknown library. | ||
final containingLibrary = element.library; | ||
if (containingLibrary == null) { | ||
return; | ||
} | ||
// Remember the element. | ||
invocationsUsages.addElementUsage(element, {arguments}); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
lib/src/analyzers/unnecessary_nullable_analyzer/models/invocations_usage.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,24 @@ | ||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/element/element.dart'; | ||
|
||
/// A container with information about used imports prefixes and used imported | ||
/// elements. | ||
class InvocationsUsage { | ||
/// The set of referenced top-level elements. | ||
final Map<Element, Set<ArgumentList>> elements = {}; | ||
|
||
final Set<String> exports = {}; | ||
|
||
void addElementUsage(Element element, Set<ArgumentList> expressions) { | ||
elements.update( | ||
element, | ||
(value) => value..addAll(expressions), | ||
ifAbsent: () => expressions, | ||
); | ||
} | ||
|
||
void merge(InvocationsUsage other) { | ||
other.elements.forEach(addElementUsage); | ||
exports.addAll(other.exports); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
lib/src/analyzers/unnecessary_nullable_analyzer/models/unnecessary_nullable_file_report.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,22 @@ | ||
import '../../../reporters/models/file_report.dart'; | ||
import 'unnecessary_nullable_issue.dart'; | ||
|
||
/// Represents unused code report collected for a file. | ||
class UnnecessaryNullableFileReport implements FileReport { | ||
/// The path to the target file. | ||
@override | ||
final String path; | ||
|
||
/// The path to the target file relative to the package root. | ||
@override | ||
final String relativePath; | ||
|
||
/// The issues detected in the target file. | ||
final Iterable<UnnecessaryNullableIssue> issues; | ||
|
||
const UnnecessaryNullableFileReport({ | ||
required this.path, | ||
required this.relativePath, | ||
required this.issues, | ||
}); | ||
} |
26 changes: 26 additions & 0 deletions
26
lib/src/analyzers/unnecessary_nullable_analyzer/models/unnecessary_nullable_issue.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,26 @@ | ||
import 'package:source_span/source_span.dart'; | ||
|
||
/// Represents an issue detected by the unused code check. | ||
class UnnecessaryNullableIssue { | ||
/// The name of the unused declaration. | ||
final String declarationName; | ||
|
||
/// The type of the unused declaration. | ||
final String declarationType; | ||
|
||
final Iterable<String> parameters; | ||
|
||
/// The source location associated with this issue. | ||
final SourceLocation location; | ||
|
||
/// Initialize a newly created [UnnecessaryNullableIssue]. | ||
/// | ||
/// The issue is associated with the given [location]. Used for | ||
/// creating an unused code report. | ||
const UnnecessaryNullableIssue({ | ||
required this.declarationName, | ||
required this.declarationType, | ||
required this.parameters, | ||
required this.location, | ||
}); | ||
} |
30 changes: 30 additions & 0 deletions
30
lib/src/analyzers/unnecessary_nullable_analyzer/reporters/reporter_factory.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,30 @@ | ||
import 'dart:io'; | ||
|
||
import '../../../reporters/models/console_reporter.dart'; | ||
import '../../../reporters/models/json_reporter.dart'; | ||
import '../../../reporters/models/reporter.dart'; | ||
import '../models/unnecessary_nullable_file_report.dart'; | ||
import 'reporters_list/console/unnecessary_nullable_console_reporter.dart'; | ||
import 'reporters_list/json/unnecessary_nullable_json_reporter.dart'; | ||
import 'unnecessary_nullable_report_params.dart'; | ||
|
||
final _implementedReports = < | ||
String, | ||
Reporter<UnnecessaryNullableFileReport, void, | ||
UnnecessaryNullableReportParams> | ||
Function( | ||
IOSink output, | ||
)>{ | ||
ConsoleReporter.id: (output) => UnnecessaryNullableConsoleReporter(output), | ||
JsonReporter.id: (output) => UnnecessaryNullableJsonReporter(output), | ||
}; | ||
|
||
Reporter<UnnecessaryNullableFileReport, void, UnnecessaryNullableReportParams>? | ||
reporter({ | ||
required String name, | ||
required IOSink output, | ||
}) { | ||
final constructor = _implementedReports[name]; | ||
|
||
return constructor != null ? constructor(output) : null; | ||
} |
62 changes: 62 additions & 0 deletions
62
...able_analyzer/reporters/reporters_list/console/unnecessary_nullable_console_reporter.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,62 @@ | ||
import 'dart:io'; | ||
|
||
import '../../../../../reporters/models/console_reporter.dart'; | ||
import '../../../models/unnecessary_nullable_file_report.dart'; | ||
import '../../unnecessary_nullable_report_params.dart'; | ||
|
||
/// Unnecessary nullable console reporter. | ||
/// | ||
/// Use it to create reports in console format. | ||
class UnnecessaryNullableConsoleReporter extends ConsoleReporter< | ||
UnnecessaryNullableFileReport, void, UnnecessaryNullableReportParams> { | ||
UnnecessaryNullableConsoleReporter(IOSink output) : super(output); | ||
|
||
@override | ||
Future<void> report( | ||
Iterable<UnnecessaryNullableFileReport> records, { | ||
Iterable<void> summary = const [], | ||
UnnecessaryNullableReportParams? additionalParams, | ||
}) async { | ||
if (records.isEmpty) { | ||
if (additionalParams?.congratulate ?? true) { | ||
output | ||
.writeln('${okPen('✔')} no unnecessary nullable parameters found!'); | ||
} | ||
|
||
return; | ||
} | ||
|
||
final sortedRecords = records.toList() | ||
..sort((a, b) => a.relativePath.compareTo(b.relativePath)); | ||
|
||
var warnings = 0; | ||
|
||
for (final analysisRecord in sortedRecords) { | ||
output.writeln('${analysisRecord.relativePath}:'); | ||
|
||
for (final issue in analysisRecord.issues) { | ||
final line = issue.location.line; | ||
final column = issue.location.column; | ||
final path = analysisRecord.relativePath; | ||
|
||
final offset = ''.padRight(3); | ||
final pathOffset = offset.padRight(5); | ||
|
||
output | ||
..writeln( | ||
'$offset ${warningPen('⚠')} ${issue.declarationType} ${issue.declarationName} has unnecessary nullable parameters', | ||
) | ||
..writeln('$pathOffset ${issue.parameters}') | ||
..writeln('$pathOffset at $path:$line:$column'); | ||
} | ||
|
||
warnings += analysisRecord.issues.length; | ||
|
||
output.writeln(''); | ||
} | ||
|
||
output.writeln( | ||
'${alarmPen('✖')} total declarations (functions, methods and constructors) with unnecessary nullable parameters - ${alarmPen(warnings)}', | ||
); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...y_nullable_analyzer/reporters/reporters_list/json/unnecessary_nullable_json_reporter.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,52 @@ | ||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import '../../../../../reporters/models/json_reporter.dart'; | ||
import '../../../models/unnecessary_nullable_file_report.dart'; | ||
import '../../../models/unnecessary_nullable_issue.dart'; | ||
import '../../unnecessary_nullable_report_params.dart'; | ||
|
||
/// Unnecessary nullable JSON reporter. | ||
/// | ||
/// Use it to create reports in JSON format. | ||
class UnnecessaryNullableJsonReporter extends JsonReporter< | ||
UnnecessaryNullableFileReport, void, UnnecessaryNullableReportParams> { | ||
const UnnecessaryNullableJsonReporter(IOSink output) : super(output, 2); | ||
|
||
@override | ||
Future<void> report( | ||
Iterable<UnnecessaryNullableFileReport> records, { | ||
Iterable<void> summary = const [], | ||
UnnecessaryNullableReportParams? additionalParams, | ||
}) async { | ||
if (records.isEmpty) { | ||
return; | ||
} | ||
|
||
final encodedReport = json.encode({ | ||
'formatVersion': formatVersion, | ||
'timestamp': getTimestamp(), | ||
'unnecessaryNullable': | ||
records.map(_unnecessaryNullableFileReportToJson).toList(), | ||
}); | ||
|
||
output.write(encodedReport); | ||
} | ||
|
||
Map<String, Object> _unnecessaryNullableFileReportToJson( | ||
UnnecessaryNullableFileReport report, | ||
) => | ||
{ | ||
'path': report.relativePath, | ||
'issues': report.issues.map(_issueToJson).toList(), | ||
}; | ||
|
||
Map<String, Object> _issueToJson(UnnecessaryNullableIssue issue) => { | ||
'declarationType': issue.declarationType, | ||
'declarationName': issue.declarationName, | ||
'parameters': issue.parameters, | ||
'column': issue.location.column, | ||
'line': issue.location.line, | ||
'offset': issue.location.offset, | ||
}; | ||
} |
6 changes: 6 additions & 0 deletions
6
...analyzers/unnecessary_nullable_analyzer/reporters/unnecessary_nullable_report_params.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,6 @@ | ||
/// Represents additional unnecessary nullable reporter params. | ||
class UnnecessaryNullableReportParams { | ||
final bool congratulate; | ||
|
||
const UnnecessaryNullableReportParams({required this.congratulate}); | ||
} |
12 changes: 12 additions & 0 deletions
12
lib/src/analyzers/unnecessary_nullable_analyzer/unnecessary_nullable_analysis_config.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,12 @@ | ||
import 'package:glob/glob.dart'; | ||
|
||
/// Represents converted unused code config which contains parsed entities. | ||
class UnnecessaryNullableAnalysisConfig { | ||
final Iterable<Glob> globalExcludes; | ||
final Iterable<Glob> analyzerExcludedPatterns; | ||
|
||
const UnnecessaryNullableAnalysisConfig( | ||
this.globalExcludes, | ||
this.analyzerExcludedPatterns, | ||
); | ||
} |
Oops, something went wrong.