blob: 561f5e6023cc5df57c8f44717e6de66d1262e97e [file] [log] [blame]
// Copyright (c) 2018, 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:analyzer_fe_comparison/src/comparison_node.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/kernel_generator.dart';
import 'package:front_end/src/api_prototype/standard_file_system.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/target/targets.dart';
/// Compiles the given [inputs] to kernel using the front_end, and returns a
/// [ComparisonNode] representing them.
Future<ComparisonNode> analyzePackage(
List<Uri> inputs, Uri packagesFileUri, Uri platformUri) async {
bool errorOccurred = false;
var component = await kernelForComponent(
inputs,
_makeCompilerOptions(packagesFileUri, platformUri, (_) {
errorOccurred = true;
}));
if (errorOccurred) {
return ComparisonNode('Error occurred');
}
var libraryNodes = <ComparisonNode>[];
var visitor = _KernelVisitor(libraryNodes);
for (var library in component.libraries) {
if (inputs.contains(library.importUri)) {
library.accept(visitor);
}
}
return ComparisonNode.sorted('Component', libraryNodes);
}
/// Compiles the given [input] to kernel using the front_end, and returns a
/// [ComparisonNode] representing it.
///
/// Only libraries whose URI passes the [uriFilter] are included in the results.
Future<ComparisonNode> analyzeProgram(Uri input, Uri packagesFileUri,
Uri platformUri, bool uriFilter(Uri uri)) async {
var errorOccurred = false;
var component = await kernelForProgram(
input,
_makeCompilerOptions(packagesFileUri, platformUri, (_) {
errorOccurred = true;
}));
if (errorOccurred) {
return ComparisonNode('Error occurred');
}
var libraryNodes = <ComparisonNode>[];
var visitor = _KernelVisitor(libraryNodes);
for (var library in component.libraries) {
if (uriFilter(library.importUri)) {
library.accept(visitor);
}
}
return ComparisonNode.sorted('Component', libraryNodes);
}
CompilerOptions _makeCompilerOptions(
Uri packagesFileUri, Uri platformUri, ErrorHandler onError) {
var targetFlags = TargetFlags(strongMode: true, syncAsync: true);
var target = NoneTarget(targetFlags);
var fileSystem = StandardFileSystem.instance;
return CompilerOptions()
..fileSystem = fileSystem
..packagesFileUri = packagesFileUri
..sdkSummary = platformUri
..strongMode = true
..target = target
..throwOnErrorsForDebugging = false
..embedSourceText = false
..onError = onError;
}
/// Visitor for serializing a kernel representation of a program into
/// ComparisonNodes.
///
/// Results are accumulated into [_resultNodes].
class _KernelVisitor extends TreeVisitor<void> {
final List<ComparisonNode> _resultNodes;
_KernelVisitor(this._resultNodes);
@override
void defaultTreeNode(TreeNode node) {
throw new UnimplementedError('KernelVisitor: ${node.runtimeType}');
}
@override
void visitClass(Class class_) {
if (class_.isAnonymousMixin) return null;
var kind = class_.isEnum
? 'Enum'
: class_.isMixinApplication ? 'MixinApplication' : 'Class';
var children = <ComparisonNode>[];
var visitor = _KernelVisitor(children);
if (class_.isEnum) {
for (var field in class_.fields) {
if (!field.isStatic) continue;
if (field.name.name == 'values') continue;
// TODO(paulberry): handle index
children.add(ComparisonNode('EnumValue ${field.name.name}'));
}
} else {
visitor._visitTypeParameters(class_.typeParameters);
if (class_.supertype != null) {
var declaredSupertype = class_.supertype.asInterfaceType;
var mixedInTypes = <DartType>[];
if (class_.isMixinApplication) {
mixedInTypes.add(class_.mixedInType.asInterfaceType);
}
while (declaredSupertype.classNode.isAnonymousMixin) {
// Since we're walking from the class to its declared supertype, we
// encounter the mixins in the reverse order that they were declared,
// so we have to use [List.insert] to add them to [mixedInTypes].
mixedInTypes.insert(
0, declaredSupertype.classNode.mixedInType.asInterfaceType);
declaredSupertype =
declaredSupertype.classNode.supertype.asInterfaceType;
}
children.add(_TypeVisitor.translate('Extends: ', declaredSupertype));
for (int i = 0; i < mixedInTypes.length; i++) {
children.add(_TypeVisitor.translate('Mixin $i: ', mixedInTypes[i]));
}
}
for (int i = 0; i < class_.implementedTypes.length; i++) {
children.add(_TypeVisitor.translate(
'Implements $i: ', class_.implementedTypes[i].asInterfaceType));
}
visitor._visitList(class_.fields);
visitor._visitList(class_.constructors);
visitor._visitList(class_.procedures);
}
// TODO(paulberry): handle more fields from Class
_resultNodes.add(ComparisonNode.sorted('$kind ${class_.name}', children));
}
@override
void visitConstructor(Constructor constructor) {
if (constructor.isSynthetic) return null;
var name = constructor.name.name;
if (name.isEmpty) {
name = '(unnamed)';
}
var children = <ComparisonNode>[];
var visitor = _KernelVisitor(children);
constructor.function.accept(visitor);
// TODO(paulberry): handle more fields from Constructor
_resultNodes.add(ComparisonNode.sorted('Constructor $name', children));
}
@override
void visitField(Field field) {
if (field.name.name == '_redirecting#') return null;
if (field.name.name == '_exports#') return null;
var children = <ComparisonNode>[];
children.add(_TypeVisitor.translate('Type: ', field.type));
// TODO(paulberry): handle more fields from Field
_resultNodes
.add(ComparisonNode.sorted('Field ${field.name.name}', children));
}
@override
void visitFunctionNode(FunctionNode node) {
var parent = node.parent;
if (!(parent is Constructor || parent is Procedure && parent.isFactory)) {
_visitTypeParameters(node.typeParameters);
_resultNodes
.add(_TypeVisitor.translate('Return type: ', node.returnType));
}
var parameterChildren = <ComparisonNode>[];
var parameterVisitor = _KernelVisitor(parameterChildren);
for (int i = 0; i < node.positionalParameters.length; i++) {
parameterVisitor._visitParameter(node.positionalParameters[i],
i < node.requiredParameterCount ? 'Required' : 'Optional');
}
for (int i = 0; i < node.namedParameters.length; i++) {
parameterVisitor._visitParameter(node.namedParameters[i], 'Named');
}
_resultNodes.add(ComparisonNode('Parameters', parameterChildren));
// TODO(paulberry): handle more fields from FunctionNode
}
@override
void visitLibrary(Library library) {
var children = <ComparisonNode>[];
if (library.name != null) {
children.add(ComparisonNode('name=${library.name}'));
}
var visitor = _KernelVisitor(children);
visitor._visitList(library.typedefs);
visitor._visitList(library.classes);
visitor._visitList(library.procedures);
visitor._visitList(library.fields);
// TODO(paulberry): handle more fields from Library
_resultNodes
.add(ComparisonNode.sorted(library.importUri.toString(), children));
}
@override
void visitProcedure(Procedure procedure) {
if (procedure.isSyntheticForwarder) {
return null;
}
if (procedure.name.name.startsWith('__loadLibrary_')) {
// Sometimes the front end generates procedures with this name that don't
// correspond to anything in the source file. Ignore them.
return null;
}
// TODO(paulberry): add an annotation to the ComparisonNode when the
// procedure is a factory.
var kind = procedure.isFactory
? 'Constructor'
: procedure.kind.toString().replaceAll('ProcedureKind.', '');
var name = procedure.name.name;
if (name.isEmpty) {
name = '(unnamed)';
}
var children = <ComparisonNode>[];
var visitor = _KernelVisitor(children);
procedure.function.accept(visitor);
// TODO(paulberry): handle more fields from Procedure
_resultNodes.add(ComparisonNode.sorted('$kind $name', children));
}
@override
void visitTypedef(Typedef typedef) {
var children = <ComparisonNode>[];
var visitor = _KernelVisitor(children);
visitor._visitTypeParameters(typedef.typeParameters);
children.add(_TypeVisitor.translate('Type: ', typedef.type));
// TODO(paulberry): handle more fields from Typedef
_resultNodes
.add(ComparisonNode.sorted('Typedef ${typedef.name}', children));
}
/// Visits all the nodes in [nodes].
void _visitList(List<TreeNode> nodes) {
for (var node in nodes) {
node.accept(this);
}
}
void _visitParameter(VariableDeclaration parameter, String kind) {
var children = <ComparisonNode>[];
children.add(_TypeVisitor.translate('Type: ', parameter.type));
// TODO(paulberry): handle more fields from VariableDeclaration
_resultNodes
.add(ComparisonNode.sorted('$kind: ${parameter.name}', children));
}
void _visitTypeParameters(List<TypeParameter> typeParameters) {
for (int i = 0; i < typeParameters.length; i++) {
_resultNodes.add(ComparisonNode(
'Type parameter $i: ${typeParameters[i].name}',
[_TypeVisitor.translate('Bound: ', typeParameters[i].bound)]));
}
}
}
/// Visitor for serializing a kernel representation of a type into
/// ComparisonNodes.
class _TypeVisitor extends DartTypeVisitor<ComparisonNode> {
/// Text to prepend to the node text.
String _prefix;
_TypeVisitor(this._prefix);
@override
ComparisonNode defaultDartType(DartType node) {
throw new UnimplementedError('_TypeVisitor: ${node.runtimeType}');
}
@override
ComparisonNode visitDynamicType(DynamicType node) {
return ComparisonNode('${_prefix}Dynamic');
}
@override
ComparisonNode visitFunctionType(FunctionType node) {
var children = <ComparisonNode>[];
var visitor = _KernelVisitor(children);
visitor._visitTypeParameters(node.typeParameters);
for (int i = 0; i < node.positionalParameters.length; i++) {
var kind = i < node.requiredParameterCount ? 'Required' : 'Optional';
children
.add(translate('$kind parameter $i: ', node.positionalParameters[i]));
}
for (var namedType in node.namedParameters) {
children.add(
translate('Named parameter ${namedType.name}: ', namedType.type));
}
return ComparisonNode.sorted('${_prefix}FunctionType', children);
}
@override
ComparisonNode visitInterfaceType(InterfaceType node) {
var children = <ComparisonNode>[];
children.add(ComparisonNode(
'Library: ${node.classNode.enclosingLibrary.importUri}'));
for (int i = 0; i < node.typeArguments.length; i++) {
children.add(translate('Type arg $i: ', node.typeArguments[i]));
}
return ComparisonNode(
'${_prefix}InterfaceType ${node.classNode.name}', children);
}
@override
ComparisonNode visitTypeParameterType(TypeParameterType node) {
// TODO(paulberry): disambiguate if needed.
return ComparisonNode(
'${_prefix}TypeParameterType: ${node.parameter.name}');
}
@override
ComparisonNode visitVoidType(VoidType node) {
return ComparisonNode('${_prefix}Void');
}
static ComparisonNode translate(String prefix, DartType type) {
return type.accept(new _TypeVisitor(prefix));
}
}