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
10 changes: 10 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_casts/avoid_unnecessary_type_casts_rule.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/fixes/avoid_unnecessary_type_casts_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 @@ -31,8 +33,11 @@ class SolidLintsPlugin extends Plugin {
final analysisLoader = AnalysisOptionsLoader();

final doubleLiteralFormatRule = DoubleLiteralFormatRule();
final avoidUnnecessaryTypeCastsRule = AvoidUnnecessaryTypeCastsRule();

final lintRules = [
AvoidFinalWithGetterRule(),
avoidUnnecessaryTypeCastsRule,
AvoidGlobalStateRule(),
AvoidNonNullAssertionRule(),
AvoidDebugPrintInReleaseRule(),
Expand All @@ -56,5 +61,10 @@ class SolidLintsPlugin extends Plugin {
AvoidFinalWithGetterRule.code,
AvoidFinalWithGetterFix.new,
);

registry.registerFixForRule(
avoidUnnecessaryTypeCastsRule.diagnosticCode,
AvoidUnnecessaryTypeCastsFix.new,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,79 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/listener.dart' as error_listener;
import 'package:analyzer/source/source_range.dart';
import 'package:custom_lint_builder/custom_lint_builder.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_casts/visitors/avoid_unnecessary_type_casts_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

part 'fixes/avoid_unnecessary_type_casts_fix.dart';

/// An `avoid_unnecessary_type_casts` rule which
/// warns about unnecessary usage of `as` operator
/// warns about unnecessary usage of the `as` operator.
///
/// A cast is unnecessary when the static type of the expression is already
/// compatible with the target type. Casting from a nullable type to a
/// non-nullable type is allowed, because it can change nullability.
///
/// {@template solid_lints.avoid_unnecessary_type_casts.example}
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// final testList = [1.0, 2.0, 3.0];
/// final result = testList as List<double>; // LINT
///
/// final testMap = {'A': 'B'};
/// final castedMapValue = testMap['A'] as String?; // LINT
///
/// final testString = 'String';
/// _testFun(testString as String); // LINT
///
/// void fun(String a) {
/// final result = (a as String).length; // LINT
/// }
/// ```
///
/// #### GOOD:
///
/// ```dart
/// final double? nullableD = 2.0;
/// // casting `Type? as Type` is allowed
/// final castedD = nullableD as double;
///
/// final testMap = {'A': 'B'};
/// final castedNotNullMapValue = testMap['A'] as String;
/// ```
/// {@endtemplate}
class AvoidUnnecessaryTypeCastsRule extends SolidLintRule {
/// This lint rule represents
/// the error whether we use bad formatted double literals.
/// The name of this lint rule.
static const lintName = 'avoid_unnecessary_type_casts';

AvoidUnnecessaryTypeCastsRule._(super.config);
/// Reported when the `as` operator is used on an expression whose static type
/// is already compatible with the cast target.
///
/// {@macro solid_lints.avoid_unnecessary_type_casts.example}
static const LintCode _code = LintCode(
lintName,
'Avoid unnecessary usage of as operator.',
correctionMessage: 'Remove the unnecessary type cast.',
);

/// Creates a new instance of [AvoidUnnecessaryTypeCastsRule]
/// based on the lint configuration.
factory AvoidUnnecessaryTypeCastsRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => "Avoid unnecessary usage of as operator.",
);
@override
LintCode get diagnosticCode => _code;

return AvoidUnnecessaryTypeCastsRule._(rule);
}
/// Creates a new instance of [AvoidUnnecessaryTypeCastsRule].
AvoidUnnecessaryTypeCastsRule()
: super(
name: lintName,
description:
'Warns about unnecessary usage of the `as` operator when the '
'static type already satisfies the cast.',
);

@override
void run(
CustomLintResolver resolver,
error_listener.DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addAsExpression((node) {
final visitor = AvoidUnnecessaryTypeCastsVisitor();
visitor.visitAsExpression(node);

for (final element in visitor.expressions.entries) {
reporter.atNode(element.key, code);
}
});
final visitor = AvoidUnnecessaryTypeCastsVisitor(this);
registry.addAsExpression(this, visitor);
}

@override
List<Fix> getFixes() => [_UnnecessaryTypeCastsFix()];
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,63 @@
part of '../avoid_unnecessary_type_casts_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_casts/avoid_unnecessary_type_casts_rule.dart';

/// A quick fix for [AvoidUnnecessaryTypeCastsRule].
///
/// Removes the `as Type` suffix from an unnecessary cast expression while
/// keeping the original expression unchanged.
///
/// ### Example
///
/// Given:
///
/// ```dart
/// final result = testList as List<double>;
/// ```
///
/// The fix produces:
///
/// ```dart
/// final result = testList;
/// ```
class AvoidUnnecessaryTypeCastsFix extends ParsedCorrectionProducer {
static const _avoidUnnecessaryTypeCastsKind = FixKind(
'solid_lints.fix.${AvoidUnnecessaryTypeCastsRule.lintName}',
DartFixKindPriority.standard,
'Remove unnecessary type cast',
);

/// A Quick fix for `avoid_unnecessary_type_casts` rule
/// Suggests to remove unnecessary assertions
class _UnnecessaryTypeCastsFix extends DartFix {
@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
Diagnostic analysisError,
List<Diagnostic> others,
) {
context.registry.addAsExpression((node) {
if (analysisError.sourceRange.intersects(node.sourceRange)) {
_addDeletion(reporter, 'as', node, node.asOperator.offset);
}
});
}
FixKind get fixKind => _avoidUnnecessaryTypeCastsKind;

@override
FixKind get multiFixKind => const FixKind(
'solid_lints.fix.multi.${AvoidUnnecessaryTypeCastsRule.lintName}',
DartFixKindPriority.standard,
'Remove unnecessary type cast across files',
);

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

@override
CorrectionApplicability get applicability =>
CorrectionApplicability.automatically;

@override
Future<void> compute(ChangeBuilder builder) async {
final asExpressionNode = node.thisOrAncestorOfType<AsExpression>();
if (asExpressionNode == null) return;

await builder.addDartFileEdit(file, (builder) {
final operatorOffset = asExpressionNode.asOperator.offset;
final targetNameLength = operatorOffset - asExpressionNode.offset;
final removedPartLength = asExpressionNode.length - targetNameLength;

void _addDeletion(
ChangeReporter reporter,
String itemToDelete,
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));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:solid_lints/src/lints/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart';
import 'package:solid_lints/src/utils/typecast_utils.dart';

/// AST Visitor which finds all as expressions and checks if they are
/// necessary
class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor<void> {
final _expressions = <Expression, String>{};
final AvoidUnnecessaryTypeCastsRule _rule;

/// All as expressions
Map<Expression, String> get expressions => _expressions;
/// Creates a new instance of [AvoidUnnecessaryTypeCastsVisitor]
AvoidUnnecessaryTypeCastsVisitor(this._rule);

@override
void visitAsExpression(AsExpression node) {
Expand All @@ -49,8 +50,8 @@ class AvoidUnnecessaryTypeCastsVisitor extends RecursiveAstVisitor<void> {
target: castedType,
);

if (typeCast.isUnnecessaryTypeCheck) {
_expressions[node] = 'as';
}
if (!typeCast.isUnnecessaryTypeCheck) return;

_rule.reportAtNode(node);
}
}
35 changes: 0 additions & 35 deletions lint_test/avoid_unnecessary_type_casts_test.dart

This file was deleted.

Loading
Loading