blob: 1f5bfeac2e6be33b5193b5181a84bee5bc4f9bda [file]
// 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 'dart:async';
import 'package:_fe_analyzer_shared/src/testing/id.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
MemberId computeMemberId(Element element) {
var enclosingElement = element.enclosingElement;
if (enclosingElement is LibraryElement) {
var memberName = element.name!;
if (element is SetterElement) {
memberName += '=';
}
return MemberId.internal(memberName);
} else if (enclosingElement is InterfaceElement) {
var memberName = element.lookupName!;
var className = enclosingElement.name;
return MemberId.internal(memberName, className: className);
} else if (enclosingElement is ExtensionElement) {
var memberName = element.name!;
var extensionName = enclosingElement.name;
if (element is PropertyAccessorElement) {
if (element is GetterElement) {
memberName = 'get#$memberName';
} else {
memberName = 'set#$memberName';
}
}
return MemberId.internal('$extensionName|$memberName');
}
throw UnimplementedError(
'TODO(paulberry): $element (${element.runtimeType})',
);
}
/// Abstract IR visitor for computing data corresponding to a node, token, or
/// element, and recording it with a generic [Id].
abstract class AstDataExtractor<T> extends UnifyingAstVisitor2<void>
with DataRegistry<T> {
final Uri uri;
@override
final ActualDataMap<T> actualMap;
AstDataExtractor(this.uri, this.actualMap);
NodeId computeDefaultNodeId(AstNode node) =>
NodeId(_nodeOffset(node), IdKind.node);
T? computeElementValue(Id id, Element element) => null;
void computeForClass(Declaration node, Id? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
void computeForFormalParameter(FormalParameter node, NodeId? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
void computeForLibrary(LibraryElement library, Id? id) {
if (id == null) return;
FutureOr<T?> value = computeElementValue(id, library);
asyncRegisterValue(uri, 0, id, value, library);
}
void computeForMember(AstNode node, Id? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
void computeForNode(AstNode node, NodeId? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
void computeForStatement(Statement node, NodeId? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
void computeForToken(Token token, NodeId? id) {
if (id == null) return;
FutureOr<T?> value = computeTokenValue(id, token);
asyncRegisterValue(uri, token.offset, id, value, token);
}
void computeForVariableDeclaration(VariableDeclaration node, NodeId? id) {
if (id == null) return;
FutureOr<T?> value = computeNodeValue(id, node);
asyncRegisterValue(uri, _nodeOffset(node), id, value, node);
}
/// Implement this to compute the data corresponding to [node].
///
/// If `null` is returned, [node] has no associated data.
FutureOr<T?> computeNodeValue(Id id, AstNode node);
/// Computes the data corresponding to [token], if any.
FutureOr<T?> computeTokenValue(Id id, Token token) => null;
Id createClassId(Declaration node) {
var element = node.declaredFragment!.element;
return ClassId(element.name!);
}
Id createLibraryId(LibraryElement node) {
Uri uri = node.uri;
if (uri.path.startsWith(r'/C:')) {
// The `MemoryResourceProvider.convertPath` inserts '/C:' on Windows.
uri = Uri(scheme: uri.scheme, path: uri.path.substring(3));
}
return LibraryId(uri);
}
Id createMemberId(FragmentDeclaringNode node) {
var element = node.declaredFragment!.element;
return computeMemberId(element);
}
NodeId createStatementId(Statement node) =>
NodeId(_nodeOffset(node), IdKind.stmt);
@override
void fail(String message) {
throw _Failure(message);
}
@override
void report(Uri uri, int offset, String message) {
// TODO(paulberry): find a way to print the error more nicely.
print('$uri:$offset: $message');
}
void run(CompilationUnit unit) {
unit.accept2(this);
}
@override
void visitCascadePropertyExtraction(CascadePropertyExtraction node) {
var propertyName = node.name;
computeForToken(propertyName, NodeId(propertyName.offset, IdKind.node));
}
@override
void visitClassDeclaration(ClassDeclaration node) {
computeForClass(node, createClassId(node));
super.visitClassDeclaration(node);
}
@override
void visitCompilationUnit(CompilationUnit node) {
var library = node.declaredFragment!.element;
computeForLibrary(library, createLibraryId(library));
super.visitCompilationUnit(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
computeForMember(node, createMemberId(node));
super.visitConstructorDeclaration(node);
}
@override
void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
// A for-in loop has no enclosing expression to attribute the write of its
// variable to, so flow analysis attributes it to the target.
var target = node.target;
computeForNode(target, computeDefaultNodeId(target));
super.visitForEachPartsWithIdentifier(node);
}
@override
void visitForElement(ForElement node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitForElement(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
if (node.parent2 is CompilationUnit) {
computeForMember(node, createMemberId(node));
}
super.visitFunctionDeclaration(node);
}
@override
void visitIfElement(IfElement node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitIfElement(node);
}
@override
void visitMapLiteralEntry(MapLiteralEntry node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitMapLiteralEntry(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
computeForMember(node, createMemberId(node));
super.visitMethodDeclaration(node);
}
@override
void visitNode(AstNode node) {
switch (node) {
case Expression():
computeForNode(node, computeDefaultNodeId(node));
case FormalParameter():
computeForFormalParameter(node, computeDefaultNodeId(node));
case ExpressionStatement():
computeForStatement(node, createStatementId(node));
case Statement():
computeForStatement(node, computeDefaultNodeId(node));
case SwitchMember():
computeForNode(node, computeDefaultNodeId(node));
}
super.visitNode(node);
}
@override
void visitNullAwareElement(NullAwareElement node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitNullAwareElement(node);
}
@override
void visitReceiverMethodInvocation(ReceiverMethodInvocation node) {
var name = node.name;
computeForToken(name, NodeId(name.offset, IdKind.node));
super.visitReceiverMethodInvocation(node);
}
@override
void visitReceiverPropertyExtraction(ReceiverPropertyExtraction node) {
// The property name is token-valued, but is a source location to which
// `IdKind.node` annotations can be attached.
var propertyName = node.name;
computeForToken(propertyName, NodeId(propertyName.offset, IdKind.node));
super.visitReceiverPropertyExtraction(node);
}
@override
void visitSpreadElement(SpreadElement node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitSpreadElement(node);
}
@override
void visitSwitchExpressionCase(SwitchExpressionCase node) {
computeForNode(node, computeDefaultNodeId(node));
super.visitSwitchExpressionCase(node);
}
@override
void visitTopLevelGetterDeclaration(TopLevelGetterDeclaration node) {
computeForMember(node, createMemberId(node));
super.visitTopLevelGetterDeclaration(node);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
if (node.parent2!.parent2 is TopLevelVariableDeclaration) {
computeForMember(node, createMemberId(node));
} else if (node.parent2!.parent2 is FieldDeclaration) {
computeForMember(node, createMemberId(node));
} else {
computeForVariableDeclaration(node, computeDefaultNodeId(node));
}
super.visitVariableDeclaration(node);
}
int _nodeOffset(AstNode node) {
int offset;
if (node is ConditionalExpression) {
offset = node.question.offset;
} else if (node is BinaryOperatorInvocation) {
offset = node.operator.offset;
} else if (node is IfNull) {
offset = node.operator.offset;
} else if (node is LogicalAnd) {
offset = node.operator.offset;
} else if (node is LogicalOr) {
offset = node.operator.offset;
} else if (node is ConstructorInvocation) {
offset = node.argumentList.leftParenthesis.offset;
} else if (node is FunctionInvocation) {
offset = node.argumentList.leftParenthesis.offset;
} else if (node is InvocationExpression) {
offset = node.argumentList.leftParenthesis.offset;
} else if (node is ReceiverPropertyExtraction) {
offset = node.name.offset;
} else if (node is SwitchExpressionCase) {
offset = node.arrow.offset;
} else {
offset = node.offset;
}
assert(offset >= 0, "No fileOffset on $node (${node.runtimeType})");
return offset;
}
}
class _Failure implements Exception {
final String? message;
_Failure([this.message]);
@override
String toString() {
if (message == null) return "Exception";
return "Exception: $message";
}
}