blob: d880ccd1480a31f52ecf97c3b442df62c3f63c38 [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/source/source_range.dart';
import 'package:analyzer/src/error/dead_code_verifier.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class RemoveDeadCode extends CorrectionProducer {
@override
// Not predictably the correct action.
bool get canBeAppliedInBulk => false;
@override
// Not predictably the correct action.
bool get canBeAppliedToFile => false;
@override
AstNode? get coveredNode {
var node = super.coveredNode;
if (node is BinaryExpression) {
var problemMessage = diagnostic?.problemMessage;
if (problemMessage != null) {
var operatorOffset = node.operator.offset;
var rightOperand = node.rightOperand;
if (problemMessage.offset == operatorOffset &&
problemMessage.length == rightOperand.end - operatorOffset) {
return rightOperand;
}
}
}
return node;
}
@override
FixKind get fixKind => DartFixKind.REMOVE_DEAD_CODE;
@override
Future<void> compute(ChangeBuilder builder) async {
final coveredNode = this.coveredNode;
var parent = coveredNode?.parent;
if (coveredNode is Expression) {
if (parent is BinaryExpression) {
if (parent.rightOperand == coveredNode) {
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(range.endEnd(parent.leftOperand, coveredNode));
});
}
} else if (parent is ForParts) {
var forStatement = parent.parent;
if (forStatement is! ForStatement) return;
await _computeForStatementParts(builder, forStatement, parent);
}
} else if (coveredNode is Block) {
var block = coveredNode;
var statementsToRemove = <Statement>[];
var problemMessage = diagnostic?.problemMessage;
if (problemMessage == null) {
return;
}
var errorRange =
SourceRange(problemMessage.offset, problemMessage.length);
for (var statement in block.statements) {
if (range.node(statement).intersects(errorRange)) {
statementsToRemove.add(statement);
}
}
if (statementsToRemove.isNotEmpty) {
var rangeToRemove = utils.getLinesRangeStatements(statementsToRemove);
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(rangeToRemove);
});
}
} else if (coveredNode is Statement) {
if (coveredNode is EmptyStatement) {
return;
}
if (coveredNode is DoStatement &&
await _computeDoStatement(builder, coveredNode)) {
return;
}
var rangeToRemove = utils.getLinesRangeStatements([coveredNode]);
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(rangeToRemove);
});
} else if (coveredNode is CatchClause && parent is TryStatement) {
var catchClauses = parent.catchClauses;
var index = catchClauses.indexOf(coveredNode);
var previous = index == 0 ? parent.body : catchClauses[index - 1];
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(range.endEnd(previous, coveredNode));
});
} else if (coveredNode is ForParts) {
var forStatement = coveredNode.parent;
if (forStatement is! ForStatement) return;
await _computeForStatementParts(builder, forStatement, coveredNode);
}
}
/// Return `true` if the fix is processed.
Future<bool> _computeDoStatement(
ChangeBuilder builder, DoStatement statement) async {
if (statement.hasBreakStatement) {
// TODO(asashour) consider modifying the do statement to a label
// https://github.com/dart-lang/sdk/issues/49091#issuecomment-1135489675
return true;
}
var problemMessage = diagnostic?.problemMessage;
if (problemMessage != null) {
var problemOffset = problemMessage.offset;
var problemLength = problemMessage.length;
var doKeyword = statement.doKeyword;
var whileKeyword = statement.whileKeyword;
Future<void> deleteNoBrackets() async {
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(range.startStart(doKeyword, doKeyword.next!));
_deleteLineRange(
builder, range.startEnd(whileKeyword, statement.semicolon));
});
}
Future<void> deleteBrackets(Block block) async {
await builder.addDartFileEdit(file, (builder) {
_deleteLineRange(
builder, range.startEnd(doKeyword, block.leftBracket));
_deleteLineRange(
builder, range.startEnd(block.rightBracket, statement.semicolon));
});
}
if (problemOffset == doKeyword.offset) {
if (problemLength == doKeyword.length) {
await deleteNoBrackets();
return true;
} else {
var body = statement.body;
if (body is Block &&
problemLength == body.leftBracket.end - problemOffset) {
await deleteBrackets(body);
return true;
}
}
} else if (problemOffset + problemLength == statement.semicolon.end) {
if (problemOffset == whileKeyword.offset) {
await deleteNoBrackets();
return true;
} else {
var body = statement.body;
if (body is Block && problemOffset == body.rightBracket.offset) {
await deleteBrackets(body);
return true;
}
}
}
}
return false;
}
Future<void> _computeForStatementParts(ChangeBuilder builder,
ForStatement forStatement, ForParts forParts) async {
var beginNode = coveredNode;
if (beginNode == null) return;
var updaters = forParts.updaters;
if (!updaters.contains(beginNode)) {
var problemMessage = diagnostic?.problemMessage;
if (problemMessage == null) return;
beginNode = null;
var problemOffset = problemMessage.offset;
var problemLength = problemMessage.length;
var updatersEnd = updaters.endToken!.end;
for (var node in updaters) {
var nodeOffset = node.offset;
if (problemOffset == nodeOffset &&
problemLength == updatersEnd - nodeOffset) {
beginNode = node;
break;
}
}
if (beginNode == null) return;
}
var isFirstNode = updaters.first == beginNode;
var rightParenthesis = forStatement.rightParenthesis;
var isComma =
!isFirstNode && rightParenthesis.previous?.type == TokenType.COMMA;
var previous = beginNode.beginToken.previous!;
var deletionRange = isComma
? range.endStart(previous, rightParenthesis)
: range.startStart(
isFirstNode ? beginNode : previous, rightParenthesis);
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(deletionRange);
});
}
void _deleteLineRange(DartFileEditBuilder builder, SourceRange sourceRange) {
builder.addDeletion(utils.getLinesRange(sourceRange));
}
}