blob: feb830de024f3c9e9f2d53e123fcbfe760be25bf [file] [log] [blame]
// Copyright (c) 2019, 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/assist.dart';
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/dart/element/element.dart';
import 'package:analyzer_plugin/utilities/assist/assist.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 ConvertToNullAware extends CorrectionProducer {
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_NULL_AWARE;
@override
FixKind get fixKind => DartFixKind.CONVERT_TO_NULL_AWARE;
@override
Future<void> compute(ChangeBuilder builder) async {
var node = this.node;
if (node.parent is BinaryExpression &&
node.parent.parent is ConditionalExpression) {
node = node.parent.parent;
}
if (node is! ConditionalExpression) {
return;
}
ConditionalExpression conditional = node;
var condition = conditional.condition.unParenthesized;
SimpleIdentifier identifier;
Expression nullExpression;
Expression nonNullExpression;
int periodOffset;
if (condition is BinaryExpression) {
//
// Identify the variable being compared to `null`, or return if the
// condition isn't a simple comparison of `null` to a variable's value.
//
var leftOperand = condition.leftOperand;
var rightOperand = condition.rightOperand;
if (leftOperand is NullLiteral && rightOperand is SimpleIdentifier) {
identifier = rightOperand;
} else if (rightOperand is NullLiteral &&
leftOperand is SimpleIdentifier) {
identifier = leftOperand;
} else {
return;
}
if (identifier.staticElement is! LocalElement) {
return;
}
//
// Identify the expression executed when the variable is `null` and when
// it is non-`null`. Return if the `null` expression isn't a null literal
// or if the non-`null` expression isn't a method invocation whose target
// is the save variable being compared to `null`.
//
if (condition.operator.type == TokenType.EQ_EQ) {
nullExpression = conditional.thenExpression;
nonNullExpression = conditional.elseExpression;
} else if (condition.operator.type == TokenType.BANG_EQ) {
nonNullExpression = conditional.thenExpression;
nullExpression = conditional.elseExpression;
}
if (nullExpression == null || nonNullExpression == null) {
return;
}
if (nullExpression.unParenthesized is! NullLiteral) {
return;
}
var unwrappedExpression = nonNullExpression.unParenthesized;
Expression target;
Token operator;
if (unwrappedExpression is MethodInvocation) {
target = unwrappedExpression.target;
operator = unwrappedExpression.operator;
} else if (unwrappedExpression is PrefixedIdentifier) {
target = unwrappedExpression.prefix;
operator = unwrappedExpression.period;
} else {
return;
}
if (operator == null || operator.type != TokenType.PERIOD) {
return;
}
if (!(target is SimpleIdentifier &&
target.staticElement == identifier.staticElement)) {
return;
}
periodOffset = operator.offset;
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(range.startStart(node, nonNullExpression));
builder.addSimpleInsertion(periodOffset, '?');
builder.addDeletion(range.endEnd(nonNullExpression, node));
});
}
}
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertToNullAware newInstance() => ConvertToNullAware();
}