blob: 6dc45249dd17f35bf0b0ee9322de246a2714da09 [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:_fe_analyzer_shared/src/testing/id.dart'
show ActualData, DataRegistry, Id, IdKind, MemberId, NodeId;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
/// Abstract IR visitor for computing data corresponding to a node or element,
/// and record it with a generic [Id]
/// TODO(paulberry): if I try to extend GeneralizingAstVisitor<void>, the VM
/// crashes.
abstract class AstDataExtractor<T> extends GeneralizingAstVisitor<dynamic>
with DataRegistry<T> {
final Uri uri;
@override
final Map<Id, ActualData<T>> actualMap;
AstDataExtractor(this.uri, this.actualMap);
NodeId computeDefaultNodeId(AstNode node) =>
NodeId(_nodeOffset(node), IdKind.node);
void computeForCollectionElement(CollectionElement node, NodeId id) {
if (id == null) return;
T value = computeNodeValue(id, node);
registerValue(uri, node.offset, id, value, node);
}
void computeForMember(Declaration node, Id id) {
if (id == null) return;
T value = computeNodeValue(id, node);
registerValue(uri, node.offset, id, value, node);
}
void computeForStatement(Statement node, NodeId id) {
if (id == null) return;
T value = computeNodeValue(id, node);
registerValue(uri, node.offset, id, value, node);
}
/// Implement this to compute the data corresponding to [node].
///
/// If `null` is returned, [node] has no associated data.
T computeNodeValue(Id id, AstNode node);
Id createMemberId(Declaration node) {
var element = node.declaredElement;
if (element.enclosingElement is CompilationUnitElement) {
var memberName = element.name;
if (element is PropertyAccessorElement && element.isSetter) {
memberName += '=';
}
return MemberId.internal(memberName);
} else if (element.enclosingElement is ClassElement) {
var memberName = element.name;
var className = element.enclosingElement.name;
return MemberId.internal(memberName, className: className);
}
throw UnimplementedError(
'TODO(paulberry): $element (${element.runtimeType})');
}
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.accept(this);
}
@override
visitCollectionElement(CollectionElement node) {
computeForCollectionElement(node, computeDefaultNodeId(node));
super.visitCollectionElement(node);
}
@override
visitConstructorDeclaration(ConstructorDeclaration node) {
computeForMember(node, createMemberId(node));
return super.visitConstructorDeclaration(node);
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
if (node.parent is CompilationUnit) {
computeForMember(node, createMemberId(node));
}
return super.visitFunctionDeclaration(node);
}
@override
visitMethodDeclaration(MethodDeclaration node) {
computeForMember(node, createMemberId(node));
return super.visitMethodDeclaration(node);
}
@override
visitStatement(Statement node) {
computeForStatement(
node,
node is ExpressionStatement
? createStatementId(node)
: computeDefaultNodeId(node));
super.visitStatement(node);
}
@override
visitVariableDeclaration(VariableDeclaration node) {
if (node.parent.parent is TopLevelVariableDeclaration) {
computeForMember(node, createMemberId(node));
} else if (node.parent.parent is FieldDeclaration) {
computeForMember(node, createMemberId(node));
}
return super.visitVariableDeclaration(node);
}
int _nodeOffset(AstNode node) {
var offset = node.offset;
assert(offset != null && offset >= 0,
"No fileOffset on $node (${node.runtimeType})");
return offset;
}
}
class _Failure implements Exception {
final String message;
_Failure([this.message]);
String toString() {
if (message == null) return "Exception";
return "Exception: $message";
}
}