blob: 566480ae7998af3d50bdf7acab24c47f7ce6b144 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class ReplaceWithIsEmpty extends CorrectionProducer {
@override
FixKind fixKind = DartFixKind.REPLACE_WITH_IS_EMPTY;
@override
FixKind multiFixKind = DartFixKind.REPLACE_WITH_IS_EMPTY_MULTI;
@override
bool get canBeAppliedInBulk => true;
@override
bool get canBeAppliedToFile => true;
@override
Future<void> compute(ChangeBuilder builder) async {
var binary = node.thisOrAncestorOfType<BinaryExpression>();
if (binary == null) {
return;
}
var replacement = _analyzeBinaryExpression(binary);
if (replacement == null) {
return;
}
fixKind = replacement.fixKind;
multiFixKind = replacement.multiFixKind;
var target = utils.getNodeText(replacement.lengthTarget);
var getter = replacement.getter;
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(range.node(binary), '$target.$getter');
});
}
static _Replacement? _analyzeBinaryExpression(BinaryExpression binary) {
var operator = binary.operator.type;
var rightValue = _getIntValue(binary.rightOperand);
if (rightValue != null) {
var lengthTarget = _getLengthTarget(binary.leftOperand);
if (lengthTarget == null) {
return null;
}
if (rightValue == 0) {
if (operator == TokenType.EQ_EQ || operator == TokenType.LT_EQ) {
return _Replacement.isEmpty(lengthTarget);
} else if (operator == TokenType.GT || operator == TokenType.BANG_EQ) {
return _Replacement.isNotEmpty(lengthTarget);
}
} else if (rightValue == 1) {
// 'length >= 1' is same as 'isNotEmpty',
// and 'length < 1' is same as 'isEmpty'
if (operator == TokenType.GT_EQ) {
return _Replacement.isNotEmpty(lengthTarget);
} else if (operator == TokenType.LT) {
return _Replacement.isEmpty(lengthTarget);
}
}
} else {
var leftValue = _getIntValue(binary.leftOperand);
if (leftValue != null) {
var lengthTarget = _getLengthTarget(binary.rightOperand);
if (lengthTarget == null) {
return null;
}
if (leftValue == 0) {
if (operator == TokenType.EQ_EQ || operator == TokenType.GT_EQ) {
return _Replacement.isEmpty(lengthTarget);
} else if (operator == TokenType.LT ||
operator == TokenType.BANG_EQ) {
return _Replacement.isNotEmpty(lengthTarget);
}
} else if (leftValue == 1) {
// '1 <= length' is same as 'isNotEmpty',
// and '1 > length' is same as 'isEmpty'
if (operator == TokenType.LT_EQ) {
return _Replacement.isNotEmpty(lengthTarget);
} else if (operator == TokenType.GT) {
return _Replacement.isEmpty(lengthTarget);
}
}
}
}
return null;
}
/// Return the value of an integer literal or prefix expression with a
/// minus and then an integer literal. For anything else, returns `null`.
static int? _getIntValue(Expression expressions) {
// Copied from package:linter/src/rules/prefer_is_empty.dart.
if (expressions is IntegerLiteral) {
return expressions.value;
} else if (expressions is PrefixExpression) {
var operand = expressions.operand;
if (expressions.operator.type == TokenType.MINUS &&
operand is IntegerLiteral) {
var value = operand.value;
if (value != null) {
return -value;
}
}
}
return null;
}
/// Return the expression producing the object on which `length` is being
/// invoked, or `null` if there is no such expression.
static Expression? _getLengthTarget(Expression expression) {
if (expression is PropertyAccess &&
expression.propertyName.name == 'length') {
return expression.target;
} else if (expression is PrefixedIdentifier &&
expression.identifier.name == 'length') {
return expression.prefix;
}
return null;
}
}
class _Replacement {
final FixKind fixKind;
final FixKind multiFixKind;
final String getter;
final Expression lengthTarget;
_Replacement.isEmpty(Expression lengthTarget)
: this._(
fixKind: DartFixKind.REPLACE_WITH_IS_EMPTY,
multiFixKind: DartFixKind.REPLACE_WITH_IS_EMPTY_MULTI,
getter: 'isEmpty',
lengthTarget: lengthTarget,
);
_Replacement.isNotEmpty(Expression lengthTarget)
: this._(
fixKind: DartFixKind.REPLACE_WITH_IS_NOT_EMPTY,
multiFixKind: DartFixKind.REPLACE_WITH_IS_NOT_EMPTY_MULTI,
getter: 'isNotEmpty',
lengthTarget: lengthTarget,
);
_Replacement._({
required this.fixKind,
required this.multiFixKind,
required this.getter,
required this.lengthTarget,
});
}