blob: dc054a85d309f6346467113332abb12b08588d62 [file] [log] [blame]
// Copyright (c) 2021, 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/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
/// Parenthesize the target of the [expressionStatement]'s expression (assumed
/// to [cascadeExpression]) before removing the cascade.
ExpressionStatement fixCascadeByParenthesizingTarget({
required ExpressionStatement expressionStatement,
required CascadeExpression cascadeExpression,
}) {
assert(cascadeExpression.cascadeSections.length == 1);
var newTarget = astFactory.parenthesizedExpression(
Token(TokenType.OPEN_PAREN, 0)
..previous = expressionStatement.beginToken.previous
..next = cascadeExpression.target.beginToken,
cascadeExpression.target,
Token(TokenType.CLOSE_PAREN, 0)
..previous = cascadeExpression.target.endToken
..next = expressionStatement.semicolon,
);
return astFactory.expressionStatement(
astFactory.cascadeExpression(
newTarget,
cascadeExpression.cascadeSections,
),
expressionStatement.semicolon,
);
}
/// Recursively insert [cascadeTarget] (the LHS of the cascade) into the
/// LHS of the assignment expression that used to be the cascade's RHS.
Expression insertCascadeTargetIntoExpression({
required Expression expression,
required Expression cascadeTarget,
}) {
// Base case: We've recursed as deep as possible.
if (expression == cascadeTarget) return cascadeTarget;
// Otherwise, copy `expression` and recurse into its LHS.
if (expression is AssignmentExpression) {
return astFactory.assignmentExpression(
insertCascadeTargetIntoExpression(
expression: expression.leftHandSide,
cascadeTarget: cascadeTarget,
),
expression.operator,
expression.rightHandSide,
);
} else if (expression is IndexExpression) {
var expressionTarget = expression.realTarget;
var question = expression.question;
// A null-aware cascade treats the `?` in `?..` as part of the token, but
// for a non-cascade index, it is a separate `?` token.
if (expression.period?.type == TokenType.QUESTION_PERIOD_PERIOD) {
question = _synthesizeToken(TokenType.QUESTION, expression.period!);
}
return astFactory.indexExpressionForTarget2(
target: insertCascadeTargetIntoExpression(
expression: expressionTarget,
cascadeTarget: cascadeTarget,
),
question: question,
leftBracket: expression.leftBracket,
index: expression.index,
rightBracket: expression.rightBracket,
);
} else if (expression is MethodInvocation) {
var expressionTarget = expression.realTarget!;
return astFactory.methodInvocation(
insertCascadeTargetIntoExpression(
expression: expressionTarget,
cascadeTarget: cascadeTarget,
),
// If we've reached the end, replace the `..` operator with `.`
expressionTarget == cascadeTarget
? _synthesizeToken(TokenType.PERIOD, expression.operator!)
: expression.operator,
expression.methodName,
expression.typeArguments,
expression.argumentList,
);
} else if (expression is PropertyAccess) {
var expressionTarget = expression.realTarget;
return astFactory.propertyAccess(
insertCascadeTargetIntoExpression(
expression: expressionTarget,
cascadeTarget: cascadeTarget,
),
// If we've reached the end, replace the `..` operator with `.`
expressionTarget == cascadeTarget
? _synthesizeToken(TokenType.PERIOD, expression.operator)
: expression.operator,
expression.propertyName,
);
}
throw UnimplementedError('Unhandled ${expression.runtimeType}'
'($expression)');
}
/// Synthesize a token with [type] to replace the given [operator].
///
/// Offset, comments, and previous/next links are all preserved.
Token _synthesizeToken(TokenType type, Token operator) =>
Token(type, operator.offset, operator.precedingComments)
..previous = operator.previous
..next = operator.next;