| // 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' |
| show CompilerOptions; |
| import 'package:front_end/src/api_prototype/diagnostic_message.dart' |
| show DiagnosticMessage, DiagnosticMessageHandler, getMessageHeaderText; |
| 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 { |
| var messages = <DiagnosticMessage>[]; |
| var component = await kernelForComponent( |
| inputs, _makeCompilerOptions(packagesFileUri, platformUri, messages.add)); |
| if (messages.isNotEmpty) { |
| return ComparisonNode( |
| 'Error occurred', messages.map(_diagnosticMessageToNode).toList()); |
| } |
| 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 messages = <DiagnosticMessage>[]; |
| var component = await kernelForProgram( |
| input, _makeCompilerOptions(packagesFileUri, platformUri, messages.add)); |
| if (messages.isNotEmpty) { |
| return ComparisonNode( |
| 'Error occurred', messages.map(_diagnosticMessageToNode).toList()); |
| } |
| 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); |
| } |
| |
| ComparisonNode _diagnosticMessageToNode(DiagnosticMessage message) { |
| return ComparisonNode(getMessageHeaderText(message)); |
| } |
| |
| CompilerOptions _makeCompilerOptions(Uri packagesFileUri, Uri platformUri, |
| DiagnosticMessageHandler onDiagnostic) { |
| var targetFlags = TargetFlags(); |
| var target = NoneTarget(targetFlags); |
| var fileSystem = StandardFileSystem.instance; |
| |
| return CompilerOptions() |
| ..fileSystem = fileSystem |
| ..packagesFileUri = packagesFileUri |
| ..sdkSummary = platformUri |
| ..target = target |
| ..throwOnErrorsForDebugging = false |
| ..embedSourceText = false |
| ..onDiagnostic = onDiagnostic; |
| } |
| |
| /// 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_.isMixinDeclaration ? 'Mixin' : '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; |
| if (class_.isMixinDeclaration) { |
| var constraints = <DartType>[]; |
| // Kernel represents a mixin declaration such as: |
| // mixin M on S0, S1, S2 {...} |
| // By desugaring it to: |
| // abstract class _M&S0&S1 implements S0, S1 {} |
| // abstract class _M&S0&S1&S2 implements _M&S0&S1 {} |
| // abstract class M extends M&S0&S1&S2 {...} |
| // (See dartbug.com/34783) |
| while (declaredSupertype.classNode.isAnonymousMixin) { |
| // Since we're walking up the class hierarchy, we encounter the |
| // mixins in the reverse order that they were declared, so we have |
| // to use [List.insert] to add them to [constraints]. |
| constraints.insert( |
| 0, |
| declaredSupertype |
| .classNode.implementedTypes[1].asInterfaceType); |
| declaredSupertype = |
| declaredSupertype.classNode.implementedTypes[0].asInterfaceType; |
| } |
| constraints.insert(0, declaredSupertype); |
| for (int i = 0; i < constraints.length; i++) { |
| children.add(_TypeVisitor.translate('On $i: ', constraints[i])); |
| } |
| } else { |
| 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); |
| children.add(translate('Return type: ', node.returnType)); |
| 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)); |
| } |
| } |