| // 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)); | |
| } | |
| } |