Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:analysis_server_plugin/registry.dart';
import 'package:solid_lints/src/lints/avoid_debug_print_in_release/avoid_debug_print_in_release_rule.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/fixes/avoid_unnecessary_type_assertions_fix.dart';
import 'package:solid_lints/src/lints/double_literal_format/double_literal_format_rule.dart';
import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
Expand Down Expand Up @@ -41,5 +43,15 @@ class SolidLintsPlugin extends Plugin {
for (final code in doubleLiteralFormatRule.diagnosticCodes) {
registry.registerFixForRule(code, DoubleLiteralFormatFix.new);
}

final avoidUnnecessaryTypeAssertionsRule =
AvoidUnnecessaryTypeAssertionsRule();
registry.registerLintRule(
avoidUnnecessaryTypeAssertionsRule,
);
registry.registerFixForRule(
avoidUnnecessaryTypeAssertionsRule.diagnosticCode,
AvoidUnnecessaryTypeAssertionsFix.new,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,134 +1,52 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
import 'package:solid_lints/src/utils/typecast_utils.dart';
import 'package:solid_lints/src/utils/types_utils.dart';

part 'fixes/avoid_unnecessary_type_assertions_fix.dart';

/// The name of 'is' operator
const operatorIsName = 'is';

/// The name of 'whereType' method
const whereTypeMethodName = 'whereType';
import 'package:analyzer/analysis_rule/analysis_rule.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_is_expression_visitor.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/visitors/unnecessary_where_type_visitor.dart';

/// Warns about unnecessary usage of `is` and `whereType` operators.
///
/// ### Example:
/// #### BAD:
/// ```dart
/// final testList = [1.0, 2.0, 3.0];
/// final result = testList is List<double>; // LINT
/// final negativeResult = testList is! List<double>; // LINT
/// testList.whereType<double>(); // LINT
///
/// final double d = 2.0;
/// final casted = d is double; // LINT
/// ```
///
/// #### GOOD:
/// ```dart
/// final dynamicList = <dynamic>[1.0, 2.0];
/// dynamicList.whereType<double>();
///
/// final double? nullableD = 2.0;
/// // casting `Type? is Type` is allowed
/// final castedD = nullableD is double;
/// ```
class AvoidUnnecessaryTypeAssertions extends SolidLintRule {
/// {@macro solid_lints.avoid_unnecessary_type_assertions.example_is}
/// {@macro solid_lints.avoid_unnecessary_type_assertions.example_where}
class AvoidUnnecessaryTypeAssertionsRule extends AnalysisRule {
/// The name of 'is' operator
static const operatorIsName = 'is';

/// The name of 'whereType' method
static const whereTypeMethodName = 'whereType';

/// This lint rule represents
/// the error whether we use bad formatted double literals.
static const lintName = 'avoid_unnecessary_type_assertions';

static const _unnecessaryIsCode = LintCode(
name: lintName,
problemMessage: "Unnecessary usage of the '$operatorIsName' operator.",
static const _unnecessaryTypeAssertionsCode = LintCode(
lintName,
"Unnecessary usage of the {0}.",
);

static const _unnecessaryWhereTypeCode = LintCode(
name: lintName,
problemMessage: "Unnecessary usage of the '$whereTypeMethodName' method.",
);

AvoidUnnecessaryTypeAssertions._(super.config);

/// Creates a new instance of [AvoidUnnecessaryTypeAssertions]
/// based on the lint configuration.
factory AvoidUnnecessaryTypeAssertions.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => "Unnecessary usage of typecast operators.",
);

return AvoidUnnecessaryTypeAssertions._(rule);
}

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
) {
context.registry.addIsExpression((node) {
if (_isUnnecessaryIsExpression(node)) {
reporter.atNode(node, _unnecessaryIsCode);
}
});
DiagnosticCode get diagnosticCode => _unnecessaryTypeAssertionsCode;

context.registry.addMethodInvocation((node) {
if (_isUnnecessaryWhereType(node)) {
reporter.atNode(node, _unnecessaryWhereTypeCode);
}
});
}
/// Creates a new instance of [AvoidUnnecessaryTypeAssertionsRule]
AvoidUnnecessaryTypeAssertionsRule()
: super(
name: lintName,
description: "Unnecessary usage of typecast operators.",
);

@override
List<Fix> getFixes() => [_UnnecessaryTypeAssertionsFix()];

bool _isUnnecessaryIsExpression(IsExpression node) {
final objectType = node.expression.staticType;
final castedType = node.type.type;

if (objectType == null || castedType == null) {
return false;
}
final typeCast = TypeCast(
source: objectType,
target: castedType,
isReversed: node.notOperator != null,
);
return typeCast.isUnnecessaryTypeCheck;
}

bool _isUnnecessaryWhereType(MethodInvocation node) {
if (node
case MethodInvocation(
methodName: Identifier(name: whereTypeMethodName),
target: Expression(staticType: final targetType),
realTarget: Expression(staticType: final realTargetType),
typeArguments: TypeArgumentList(arguments: final arguments),
)
when targetType is ParameterizedType &&
isIterable(realTargetType) &&
arguments.isNotEmpty) {
final objectType = targetType.typeArguments.first;
final castedType = arguments.first.type;

if (castedType == null) {
return false;
}
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
super.registerNodeProcessors(registry, context);

final typeCast = TypeCast(source: objectType, target: castedType);
final unnecessaryIsExpressionVisitor = UnnecessaryIsExpressionVisitor(this);
registry.addIsExpression(this, unnecessaryIsExpressionVisitor);

return typeCast.isUnnecessaryTypeCheck;
} else {
return false;
}
final unnecessaryWhereTypeVisitor = UnnecessaryWhereTypeVisitor(this);
registry.addMethodInvocation(this, unnecessaryWhereTypeVisitor);
}
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,68 @@
part of '../avoid_unnecessary_type_assertions_rule.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';

/// A Quick fix for `avoid_unnecessary_type_assertions` rule
/// Suggests to remove unnecessary assertions
class _UnnecessaryTypeAssertionsFix extends DartFix {
class AvoidUnnecessaryTypeAssertionsFix extends ResolvedCorrectionProducer {
static const _avoidUnnecessaryTypeAssertionsFixKind = FixKind(
'solid_lints.fix.${AvoidUnnecessaryTypeAssertionsRule.lintName}',
DartFixKindPriority.standard,
'Remove the unnecessary {0}',
);

SourceRange? _partToRemove;

@override
List<String>? get fixArguments => [
if (_partToRemove case final partToRemove?)
'"${utils.getRangeText(partToRemove).trim()}"'
else
'type assertion',
];

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
Diagnostic analysisError,
List<Diagnostic> others,
) {
context.registry.addIsExpression((node) {
if (analysisError.sourceRange.intersects(node.sourceRange)) {
_addDeletion(reporter, operatorIsName, node, node.isOperator.offset);
}
});

context.registry.addMethodInvocation((node) {
if (analysisError.sourceRange.intersects(node.sourceRange)) {
_addDeletion(
reporter,
whereTypeMethodName,
node,
node.operator?.offset ?? node.offset,
);
}
});
FixKind get fixKind => _avoidUnnecessaryTypeAssertionsFixKind;

@override
CorrectionApplicability get applicability =>
CorrectionApplicability.automatically;

/// Creates a new instance of [AvoidUnnecessaryTypeAssertionsFix]
AvoidUnnecessaryTypeAssertionsFix({required super.context});

@override
Future<void> compute(ChangeBuilder builder) async {
final isExpressionNode = node.thisOrAncestorOfType<IsExpression>();
if (isExpressionNode != null) {
final operatorOffset = isExpressionNode.isOperator.offset - 1;
_partToRemove = _removedPartRange(isExpressionNode, operatorOffset);
}

final whereTypeNode = node.thisOrAncestorOfType<MethodInvocation>();
if (whereTypeNode != null && _partToRemove == null) {
final operatorOffset =
whereTypeNode.operator?.offset ?? whereTypeNode.offset;
_partToRemove = _removedPartRange(whereTypeNode, operatorOffset);
}

final partToRemove = _partToRemove;
if (partToRemove == null) return;

await builder.addDartFileEdit(
file,
(builder) => builder.addDeletion(partToRemove),
);
}
Comment thread
andrew-bekhiet-solid marked this conversation as resolved.

void _addDeletion(
ChangeReporter reporter,
String itemToDelete,
Expression node,
int operatorOffset,
) {
SourceRange _removedPartRange(Expression node, int operatorOffset) {
final targetNameLength = operatorOffset - node.offset;
final removedPartLength = node.length - targetNameLength;

final changeBuilder = reporter.createChangeBuilder(
message: "Remove unnecessary '$itemToDelete'",
priority: 1,
);

changeBuilder.addDartFileEdit((builder) {
builder.addDeletion(SourceRange(operatorOffset, removedPartLength));
});
return SourceRange(operatorOffset, removedPartLength);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
import 'package:solid_lints/src/utils/typecast_utils.dart';

/// Visitor for [AvoidUnnecessaryTypeAssertionsRule].
/// Reports on unnecessary usage of 'is' operator.
///
/// ### Example:
/// {@template solid_lints.avoid_unnecessary_type_assertions.example_is}
/// #### BAD:
/// ```dart
/// final testList = [1.0, 2.0, 3.0];
/// final result = testList is List<double>; // LINT
/// final negativeResult = testList is! List<double>; // LINT
///
/// final double d = 2.0;
/// final casted = d is double; // LINT
/// ```
///
/// #### GOOD:
/// ```dart
/// final double? nullableD = 2.0;
/// // casting `Type? is Type` is allowed
/// final castedD = nullableD is double;
/// ```
/// {@endtemplate}
class UnnecessaryIsExpressionVisitor extends SimpleAstVisitor<void> {
final AvoidUnnecessaryTypeAssertionsRule _rule;

/// Creates a new instance of [UnnecessaryIsExpressionVisitor].
UnnecessaryIsExpressionVisitor(this._rule);

@override
void visitIsExpression(IsExpression node) {
super.visitIsExpression(node);

if (!_isUnnecessaryIsExpression(node)) return;

_rule.reportAtNode(
node,
arguments: [
"'${AvoidUnnecessaryTypeAssertionsRule.operatorIsName}' operator",
],
);
}

bool _isUnnecessaryIsExpression(IsExpression node) {
final objectType = node.expression.staticType;
final castedType = node.type.type;

if (objectType == null || castedType == null) {
return false;
}

final typeCast = TypeCast(
source: objectType,
target: castedType,
isReversed: node.notOperator != null,
);
return typeCast.isUnnecessaryTypeCheck;
}
}
Loading
Loading