blob: 62330eef44374f4220bd55b8e5efa3027d679357 [file] [log] [blame]
// Copyright (c) 2015, 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/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/scanner.dart';
import 'ast_builder.dart';
/// Creates an index of variable assignments and declarations occurring in all
/// the provided [nodes].
///
/// Expands any compound assignment expression (e.g. `i += 2` yields an assigned
/// expression of `i + 2`), and treats declarations with no assignment as having
/// a `null` assigned expression.
Map<LocalVariableElement, List<Expression>> indexLocalAssignments(
Iterable<AstNode> nodes) {
var visitor = new _LocalAssignmentsVisitor();
nodes.forEach((n) => n.accept(visitor));
return visitor.assignedExpressions;
}
/// Visits variable declarations and assignments and exposes an
/// [AssignmentIndex] interface.
///
// TODO(ochafik): Introduce flow analysis (a variable may be nullable in
// some places and not in others).
class _LocalAssignmentsVisitor extends RecursiveAstVisitor {
final assignedExpressions = <LocalVariableElement, List<Expression>>{};
@override
visitVariableDeclaration(VariableDeclaration node) {
var element = node.element;
if (element is LocalVariableElement) {
_addAssignment(element, node.initializer ?? AstBuilder.nullLiteral());
}
super.visitVariableDeclaration(node);
}
@override
visitCatchClause(CatchClause node) {
for (var ident in [node.exceptionParameter, node.stackTraceParameter]) {
if (ident == null) continue;
assert(ident.staticElement is LocalVariableElement);
_addAssignment(ident.staticElement, AstBuilder.nullLiteral());
}
}
@override
visitAssignmentExpression(AssignmentExpression node) {
var lhs = node.leftHandSide;
var e = _getLocalVariable(lhs);
if (e != null) _addAssignment(e, node.rightHandSide);
super.visitAssignmentExpression(node);
}
@override
visitBinaryExpression(BinaryExpression node) {
var op = node.operator.type;
if (op.isAssignmentOperator) {
var e = _getLocalVariable(node.leftOperand);
if (e != null) {
// TODO(ochafik): Once we have non-nullable types, compute the static
// type for this AST node.
_addAssignment(
e,
RawAstBuilder.binaryExpression(node.leftOperand,
_opToken(_expandAssignmentOp(op)), node.rightOperand));
}
}
super.visitBinaryExpression(node);
}
@override
visitPostfixExpression(PostfixExpression node) {
var op = node.operator.type;
if (op.isAssignmentOperator) {
// Treat `x++` as statically assigning `x + 1` to variable `x`.
var e = _getLocalVariable(node.operand);
if (e != null) _handleIncrOrDecr(e, node.operand, op);
}
super.visitPostfixExpression(node);
}
@override
visitPrefixExpression(PrefixExpression node) {
var op = node.operator.type;
if (op.isAssignmentOperator) {
// Treat `++x` as statically assigning `x + 1` to variable `x`.
var e = _getLocalVariable(node.operand);
if (e != null) _handleIncrOrDecr(e, node.operand, op);
}
super.visitPrefixExpression(node);
}
/// Note: we're not interested in differences between prefix & suffix.
_handleIncrOrDecr(LocalVariableElement e, Expression operand, TokenType op) {
if (!op.isIncrementOperator) throw new ArgumentError('Unexpected op: $op');
// TODO(ochafik): Once we have non-nullable types, compute the static
// type for this AST node.
_addAssignment(
e,
RawAstBuilder.binaryExpression(
operand, _opToken(op), AstBuilder.integerLiteral(1)));
}
void _addAssignment(VariableElement e, Expression value) =>
assignedExpressions.putIfAbsent(e, () => <Expression>[]).add(value);
LocalVariableElement _getLocalVariable(Expression target) {
if (target is SimpleIdentifier) {
var e = target.bestElement;
if (e is LocalVariableElement && e is! PropertyAccessorElement) {
return e;
}
}
return null;
}
}
const Map<TokenType, TokenType> _opByAssignmentOp = const {
TokenType.AMPERSAND_EQ: TokenType.AMPERSAND,
TokenType.BAR_EQ: TokenType.BAR,
TokenType.CARET_EQ: TokenType.CARET,
TokenType.GT_GT_EQ: TokenType.GT_GT,
TokenType.LT_LT_EQ: TokenType.LT_LT,
TokenType.MINUS_EQ: TokenType.MINUS,
TokenType.PERCENT_EQ: TokenType.PERCENT,
TokenType.PLUS_EQ: TokenType.PLUS,
TokenType.QUESTION_QUESTION_EQ: TokenType.QUESTION_QUESTION,
TokenType.SLASH_EQ: TokenType.SLASH,
TokenType.STAR_EQ: TokenType.STAR,
TokenType.TILDE_SLASH_EQ: TokenType.TILDE_SLASH,
};
Token _opToken(TokenType t) => new Token(t, 0);
/// Transforms `+=` to `+`, `??=` to `??`, etc.
TokenType _expandAssignmentOp(TokenType assignmentOp) {
assert(assignmentOp.isAssignmentOperator);
var op = _opByAssignmentOp[assignmentOp];
if (op == null) throw new ArgumentError("Can't expand op $assignmentOp");
return op;
}