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
11 changes: 11 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'package:solid_lints/src/lints/cyclomatic_complexity/cyclomatic_complexit
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/function_lines_of_code/function_lines_of_code_rule.dart';
import 'package:solid_lints/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.dart';
import 'package:solid_lints/src/lints/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart';
import 'package:solid_lints/src/lints/prefer_first/fixes/prefer_first_fix.dart';
import 'package:solid_lints/src/lints/prefer_first/prefer_first_rule.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
Expand Down Expand Up @@ -42,6 +44,9 @@ class SolidLintsPlugin extends Plugin {
AvoidUnnecessaryTypeAssertionsRule();
final doubleLiteralFormatRule = DoubleLiteralFormatRule();
final preferFirstRule = PreferFirstRule();
final preferConditionalExpressionsRule = PreferConditionalExpressionsRule(
analysisOptionsLoader: analysisLoader,
);

final lintRules = [
AvoidFinalWithGetterRule(),
Expand All @@ -65,6 +70,7 @@ class SolidLintsPlugin extends Plugin {
),
UseNearestContextRule(),
preferFirstRule,
preferConditionalExpressionsRule,
// TODO: Add more lint rules and use analysisLoader
// for rules that need parameters
// For example: `CyclomaticComplexityRule(analysisLoader)`
Expand Down Expand Up @@ -97,5 +103,10 @@ class SolidLintsPlugin extends Plugin {
preferFirstRule.diagnosticCode,
PreferFirstFix.new,
);

registry.registerFixForRule(
preferConditionalExpressionsRule.diagnosticCode,
PreferConditionalExpressionsFix.new,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,56 +1,55 @@
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/dart/ast/token.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:custom_lint_builder/custom_lint_builder.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/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart';
import 'package:solid_lints/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart';

/// A Quick fix for `prefer_conditional_expressions` rule
/// Suggests to remove unnecessary assertions
class PreferConditionalExpressionsFix extends DartFix {
final Expando<StatementInfo> _diagnosticsInfoExpando;
/// Suggests to convert simple if statements to conditional expressions
class PreferConditionalExpressionsFix extends ResolvedCorrectionProducer {
static const _fixComment = "Convert to conditional expression.";

/// A Quick fix for `prefer_conditional_expressions` rule
/// Suggests to remove unnecessary assertions
PreferConditionalExpressionsFix(this._diagnosticsInfoExpando);
/// Creates a new instance of [PreferConditionalExpressionsFix]
PreferConditionalExpressionsFix({required super.context});

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
Diagnostic diagnostic,
List<Diagnostic> others,
) {
context.registry.addIfStatement((node) {
if (!diagnostic.sourceRange.intersects(node.sourceRange)) return;
FixKind get fixKind => const FixKind(
'solid_lints.fix.${PreferConditionalExpressionsRule.lintName}',
DartFixKindPriority.standard,
_fixComment,
);

final statementInfo = _diagnosticsInfoExpando[diagnostic];
if (statementInfo == null) return;
@override
FixKind get multiFixKind => const FixKind(
'solid_lints.fix.multi.${PreferConditionalExpressionsRule.lintName}',
DartFixKindPriority.standard,
'$_fixComment across files',
);

final correction = _createCorrection(statementInfo);
if (correction == null) return;
@override
CorrectionApplicability get applicability =>
CorrectionApplicability.automatically;

_addReplacement(reporter, statementInfo.statement, correction);
});
}
@override
Future<void> compute(ChangeBuilder builder) async {
final statement = node.thisOrAncestorOfType<IfStatement>();
if (statement == null) return;

void _addReplacement(
ChangeReporter reporter,
IfStatement node,
String correction,
) {
final changeBuilder = reporter.createChangeBuilder(
message: "Convert to conditional expression.",
priority: 1,
);
final statementInfo = StatementInfo.fromIfStatement(statement);
if (statementInfo == null) return;

changeBuilder.addDartFileEdit((builder) {
builder.addSimpleReplacement(
SourceRange(node.offset, node.length),
final correction = _createCorrection(statementInfo);
if (correction == null) return;

await builder.addDartFileEdit(
file,
(builder) => builder.addSimpleReplacement(
statement.sourceRange,
correction,
);
});
),
);
}

String? _createCorrection(StatementInfo info) {
Expand All @@ -64,28 +63,15 @@ class PreferConditionalExpressionsFix extends DartFix {
final target = thenStatement.leftHandSide;
final firstExpression = thenStatement.rightHandSide;
final secondExpression = elseStatement.rightHandSide;

final thenStatementOperator = thenStatement.operator.type;
final elseStatementOperator = elseStatement.operator.type;

if (_isAssignmentOperatorNotEq(thenStatementOperator) &&
_isAssignmentOperatorNotEq(elseStatementOperator)) {
final prefix = thenStatement.leftHandSide;
final thenPart =
'$prefix ${thenStatementOperator.stringValue} $firstExpression';
final elsePart =
'$prefix ${elseStatementOperator.stringValue} $secondExpression;';

return '$condition ? $thenPart : $elsePart';
}
final op = thenStatement.operator.lexeme;

final correctionForLiterals = _createCorrectionForLiterals(
condition,
firstExpression,
secondExpression,
);

return '$target = $correctionForLiterals';
return '$target $op $correctionForLiterals';
}

if (thenStatement is ReturnStatement && elseStatement is ReturnStatement) {
Expand All @@ -110,14 +96,23 @@ class PreferConditionalExpressionsFix extends DartFix {
) {
if (firstExpression is BooleanLiteral &&
secondExpression is BooleanLiteral) {
if (firstExpression.value == secondExpression.value) {
return '${firstExpression.value};';
}
final isInverted = !firstExpression.value && secondExpression.value;
if (isInverted) {
final useParentheses =
condition is! Identifier &&
condition is! PropertyAccess &&
condition is! MethodInvocation &&
condition is! IndexExpression &&
condition is! ParenthesizedExpression;
return '${useParentheses ? '!($condition)' : '!$condition'};';
}

return '${isInverted ? "!" : ""}$condition;';
return '$condition;';
}

return '$condition ? $firstExpression : $secondExpression;';
}

bool _isAssignmentOperatorNotEq(TokenType token) =>
token.isAssignmentOperator && token != TokenType.EQ;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ class PreferConditionalExpressionsParameters {
required this.ignoreNested,
});

/// Empty [PreferConditionalExpressionsParameters] model.
factory PreferConditionalExpressionsParameters.empty() =>
const PreferConditionalExpressionsParameters(
ignoreNested: false,
);

/// Method for creating from json data
factory PreferConditionalExpressionsParameters.fromJson(
Map<String, Object?> json,
Expand All @@ -28,3 +34,4 @@ class PreferConditionalExpressionsParameters {
ignoreNested: json[_ignoreNestedConfig] as bool? ?? false,
);
}

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/prefer_conditional_expressions/fixes/prefer_conditional_expressions_fix.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/prefer_conditional_expressions/models/prefer_conditional_expressions_parameters.dart';
import 'package:solid_lints/src/lints/prefer_conditional_expressions/visitors/prefer_conditional_expressions_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

// Inspired by TSLint (https://palantir.github.io/tslint/rules/prefer-conditional-expression/)
Expand All @@ -14,10 +13,11 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// ### Example config:
///
/// ```yaml
/// custom_lint:
/// rules:
/// - prefer_conditional_expressions:
/// ignore_nested: true
/// plugins:
/// solid_lints:
/// diagnostics:
/// prefer_conditional_expressions:
/// ignore_nested: true
/// ```
///
/// ### Example
Expand Down Expand Up @@ -57,51 +57,43 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// ```
class PreferConditionalExpressionsRule
extends SolidLintRule<PreferConditionalExpressionsParameters> {
/// This lint rule represents the error if number of
/// parameters reaches the maximum value.
/// This lint rule represents the error when an if-else statement
/// can be simplified to a conditional expression.
static const lintName = 'prefer_conditional_expressions';

final _diagnosticsInfoExpando = Expando<StatementInfo>();
static const _code = LintCode(
lintName,
'Prefer conditional expression.',
);

PreferConditionalExpressionsRule._(super.config);
@override
LintCode get diagnosticCode => _code;

/// Creates a new instance of [PreferConditionalExpressionsRule]
/// based on the lint configuration.
factory PreferConditionalExpressionsRule.createRule(
CustomLintConfigs configs,
) {
final config = RuleConfig(
configs: configs,
name: lintName,
paramsParser: PreferConditionalExpressionsParameters.fromJson,
problemMessage: (value) => 'Prefer conditional expression.',
);

return PreferConditionalExpressionsRule._(config);
}
PreferConditionalExpressionsRule({
required super.analysisOptionsLoader,
}) : super.withParameters(
name: _code.lowerCaseName,
description: _code.problemMessage,
parametersParser: PreferConditionalExpressionsParameters.fromJson,
);

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = PreferConditionalExpressionsVisitor(
ignoreNested: config.parameters.ignoreNested,
);
node.accept(visitor);
super.registerNodeProcessors(registry, context);

for (final element in visitor.statementsInfo) {
final diagnostic = reporter.atNode(element.statement, code);
final parameters =
getParametersForContext(context) ??
PreferConditionalExpressionsParameters.empty();

_diagnosticsInfoExpando[diagnostic] = element;
}
});
}
final visitor = PreferConditionalExpressionsVisitor(
rule: this,
ignoreNested: parameters.ignoreNested,
);

@override
List<Fix> getFixes() => [
PreferConditionalExpressionsFix(_diagnosticsInfoExpando),
];
registry.addCompilationUnit(this, visitor);
}
}
Loading
Loading