blob: 6bc0d16eb4dce72a088e546bb56eb19cac5120a7 [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:analyzer/dart/ast/ast.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' show SourceEdit;
import 'package:nnbd_migration/src/conditional_discard.dart';
import 'package:nnbd_migration/src/nullability_node.dart';
/// Records information about how a conditional expression or statement might
/// need to be modified.
class ConditionalModification extends PotentialModification {
final int offset;
final int end;
final bool isStatement;
final ConditionalDiscard discard;
final _KeepNode condition;
final _KeepNode thenStatement;
final _KeepNode elseStatement;
factory ConditionalModification(AstNode node, ConditionalDiscard discard) {
if (node is IfStatement) {
return ConditionalModification._(
node.offset,
node.end,
node is Statement,
discard,
_KeepNode(node.condition),
_KeepNode(node.thenStatement),
_KeepNode(node.elseStatement));
} else {
throw new UnimplementedError('TODO(paulberry)');
}
}
ConditionalModification._(this.offset, this.end, this.isStatement,
this.discard, this.condition, this.thenStatement, this.elseStatement);
@override
bool get isEmpty => discard.keepTrue && discard.keepFalse;
@override
Iterable<SourceEdit> get modifications {
if (isEmpty) return const [];
// TODO(paulberry): move the following logic into DartEditBuilder (see
// dartbug.com/35872).
var result = <SourceEdit>[];
var keepNodes = <_KeepNode>[];
if (!discard.pureCondition) {
keepNodes.add(condition); // TODO(paulberry): test
}
if (discard.keepTrue) {
keepNodes.add(thenStatement); // TODO(paulberry): test
}
if (discard.keepFalse) {
keepNodes.add(elseStatement); // TODO(paulberry): test
}
// TODO(paulberry): test thoroughly
for (int i = 0; i < keepNodes.length; i++) {
var keepNode = keepNodes[i];
if (i == 0 && keepNode.offset != offset) {
result.add(SourceEdit(offset, 0, '/* '));
}
if (i != 0 || keepNode.offset != offset) {
result.add(SourceEdit(keepNode.offset, 0, '*/ '));
}
if (i != keepNodes.length - 1 || keepNode.end != end) {
result.add(SourceEdit(keepNode.end, 0,
keepNode.isExpression && isStatement ? '; /*' : ' /*'));
}
if (i == keepNodes.length - 1 && keepNode.end != end) {
result.add(SourceEdit(end, 0, ' */'));
}
}
return result;
}
}
/// Records information about the possible addition of an import
/// to the source code.
class PotentiallyAddImport extends PotentialModification {
final _usages = <PotentialModification>[];
final int _offset;
final String _importPath;
PotentiallyAddImport(
AstNode beforeNode, this._importPath, PotentialModification usage)
: _offset = beforeNode.offset {
_usages.add(usage);
}
get importPath => _importPath;
@override
bool get isEmpty {
for (PotentialModification usage in _usages) {
if (!usage.isEmpty) {
return false;
}
}
return true;
}
// TODO(danrubel): change all of dartfix NNBD to use DartChangeBuilder
@override
Iterable<SourceEdit> get modifications =>
isEmpty ? const [] : [SourceEdit(_offset, 0, "import '$_importPath';\n")];
void addUsage(PotentialModification usage) {
_usages.add(usage);
}
}
/// Records information about the possible addition of a `@required` annotation
/// to the source code.
class PotentiallyAddRequired extends PotentialModification {
final NullabilityNode _node;
final int _offset;
PotentiallyAddRequired(DefaultFormalParameter parameter, this._node)
: _offset = parameter.offset;
@override
bool get isEmpty => _node.isNullable;
@override
Iterable<SourceEdit> get modifications =>
isEmpty ? const [] : [SourceEdit(_offset, 0, '@required ')];
}
/// Interface used by data structures representing potential modifications to
/// the code being migrated.
abstract class PotentialModification {
bool get isEmpty;
/// Gets the individual migrations that need to be done, considering the
/// solution to the constraint equations.
Iterable<SourceEdit> get modifications;
}
/// Helper object used by [ConditionalModification] to keep track of AST nodes
/// within the conditional expression.
class _KeepNode {
final int offset;
final int end;
final bool isExpression;
factory _KeepNode(AstNode node) {
int offset = node.offset;
int end = node.end;
if (node is Block && node.statements.isNotEmpty) {
offset = node.statements.beginToken.offset;
end = node.statements.endToken.end;
}
return _KeepNode._(offset, end, node is Expression);
}
_KeepNode._(this.offset, this.end, this.isExpression);
}