Skip to content
9 changes: 9 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.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 All @@ -30,11 +32,14 @@ class SolidLintsPlugin extends Plugin {
void register(PluginRegistry registry) {
final analysisLoader = AnalysisOptionsLoader();

final avoidUnnecessaryTypeAssertionsRule =
AvoidUnnecessaryTypeAssertionsRule();
final doubleLiteralFormatRule = DoubleLiteralFormatRule();
final lintRules = [
AvoidFinalWithGetterRule(),
AvoidGlobalStateRule(),
AvoidNonNullAssertionRule(),
avoidUnnecessaryTypeAssertionsRule,
AvoidDebugPrintInReleaseRule(),
doubleLiteralFormatRule,
ProperSuperCallsRule(),
Expand All @@ -56,5 +61,9 @@ class SolidLintsPlugin extends Plugin {
AvoidFinalWithGetterRule.code,
AvoidFinalWithGetterFix.new,
);
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