| // 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; |