blob: 67174a4e2c522cb91c2820ac4f137cd6f969a8e9 [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/dart/ast/precedence.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:nnbd_migration/src/edit_plan.dart';
/// Implementation of [NodeChange] representing the addition of the keyword
/// `required` to a named parameter.
///
/// TODO(paulberry): store additional information necessary to include in the
/// preview.
class AddRequiredKeyword extends _NestableChange {
const AddRequiredKeyword([NodeChange inner = const NoChange()])
: super(inner);
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
var innerPlan = _inner.apply(node, aggregator);
return aggregator.planner
.surround(innerPlan, prefix: [const InsertText('required ')]);
}
}
/// Visitor that combines together the changes produced by [FixBuilder] into a
/// concrete set of source code edits using the infrastructure of [EditPlan].
class FixAggregator extends UnifyingAstVisitor<void> {
/// Map from the [AstNode]s that need to have changes made, to the changes
/// that need to be applied to them.
final Map<AstNode, NodeChange> _changes;
/// The set of [EditPlan]s being accumulated.
List<EditPlan> _plans = [];
final EditPlanner planner;
FixAggregator._(this.planner, this._changes);
/// Gathers all the changes to nodes descended from [node] into a single
/// [EditPlan].
EditPlan innerPlanForNode(AstNode node) {
var previousPlans = _plans;
try {
_plans = [];
node.visitChildren(this);
return planner.passThrough(node, innerPlans: _plans);
} finally {
_plans = previousPlans;
}
}
@override
void visitNode(AstNode node) {
var change = _changes[node];
if (change != null) {
var innerPlan = change.apply(node, this);
if (innerPlan != null) {
_plans.add(innerPlan);
}
} else {
node.visitChildren(this);
}
}
/// Runs the [FixAggregator] on a [unit] and returns the resulting edits.
static Map<int, List<AtomicEdit>> run(
CompilationUnit unit, Map<AstNode, NodeChange> changes) {
var planner = EditPlanner();
var aggregator = FixAggregator._(planner, changes);
unit.accept(aggregator);
if (aggregator._plans.isEmpty) return {};
EditPlan plan;
if (aggregator._plans.length == 1) {
plan = aggregator._plans[0];
} else {
plan = planner.passThrough(unit, innerPlans: aggregator._plans);
}
return plan.finalize();
}
}
/// Implementation of [NodeChange] representing introduction of an explicit
/// downcast.
///
/// TODO(paulberry): store additional information necessary to include in the
/// preview.
class IntroduceAs extends _NestableChange {
/// TODO(paulberry): shouldn't be a String
final String type;
const IntroduceAs(this.type, [NodeChange inner = const NoChange()])
: super(inner);
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
var innerPlan = _inner.apply(node, aggregator);
return aggregator.planner.surround(innerPlan,
suffix: [InsertText(' as $type')],
outerPrecedence: Precedence.relational,
innerPrecedence: Precedence.relational);
}
}
/// Implementation of [NodeChange] representing the addition of a trailing `?`
/// to a type.
///
/// TODO(paulberry): store additional information necessary to include in the
/// preview.
class MakeNullable extends _NestableChange {
const MakeNullable([NodeChange inner = const NoChange()]) : super(inner);
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
var innerPlan = _inner.apply(node, aggregator);
return aggregator.planner
.surround(innerPlan, suffix: [const InsertText('?')]);
}
}
/// Implementation of [NodeChange] representing no change at all. This class
/// is intended to be used as a base class for changes that wrap around other
/// changes.
class NoChange extends NodeChange {
const NoChange();
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
return aggregator.innerPlanForNode(node);
}
}
/// Base class representing a kind of change that [FixAggregator] might make to a
/// particular AST node.
abstract class NodeChange {
const NodeChange();
/// Applies this change to the given [node], producing an [EditPlan]. The
/// [aggregator] may be used to gather up any edits to the node's descendants
/// into their own [EditPlan]s.
///
/// Note: the reason the caller can't just gather up the edits and pass them
/// in is that some changes don't preserve all of the structure of the nodes
/// below them (e.g. dropping an unnecessary cast), so those changes need to
/// be able to call the appropriate [aggregator] methods just on the nodes
/// they need.
EditPlan apply(AstNode node, FixAggregator aggregator);
}
/// Implementation of [NodeChange] representing the addition of a null check to
/// an expression.
///
/// TODO(paulberry): store additional information necessary to include in the
/// preview.
class NullCheck extends _NestableChange {
const NullCheck([NodeChange inner = const NoChange()]) : super(inner);
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
var innerPlan = _inner.apply(node, aggregator);
return aggregator.planner.surround(innerPlan,
suffix: [const InsertText('!')],
outerPrecedence: Precedence.postfix,
innerPrecedence: Precedence.postfix,
associative: true);
}
}
/// Implementation of [NodeChange] representing the removal of an unnecessary
/// cast.
///
/// TODO(paulberry): store additional information necessary to include in the
/// preview.
class RemoveAs extends _NestableChange {
const RemoveAs([NodeChange inner = const NoChange()]) : super(inner);
@override
EditPlan apply(AstNode node, FixAggregator aggregator) {
return aggregator.planner.extract(
node, _inner.apply((node as AsExpression).expression, aggregator));
}
}
/// Shared base class for [NodeChange]s that are based on an [_inner] change.
abstract class _NestableChange extends NodeChange {
final NodeChange _inner;
const _NestableChange(this._inner);
}