| // 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/dart/analysis/analysis_context_collection.dart'; |
| import 'package:analyzer/dart/analysis/context_root.dart'; |
| import 'package:analyzer/dart/analysis/session.dart'; |
| import 'package:analyzer/dart/analysis/uri_converter.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/source.dart' show SourceKind; |
| import 'package:analyzer_fe_comparison/src/comparison_node.dart'; |
| |
| /// Analyzes the files in [filePaths] using the analyzer, and returns a |
| /// [ComparisonNode] representing them. |
| Future<ComparisonNode> analyzeFiles( |
| String startingPath, List<String> filePaths) async { |
| var driver = await _AnalyzerDriver.create(startingPath); |
| return driver.analyzeFiles(filePaths); |
| } |
| |
| /// Analyzes the package located at [libPath] using the analyzer, and returns a |
| /// [ComparisonNode] representing it. |
| Future<ComparisonNode> analyzePackage(String libPath) async { |
| var driver = await _AnalyzerDriver.create(libPath); |
| return driver.analyzePackage(); |
| } |
| |
| class _AnalyzerDriver { |
| final AnalysisSession _session; |
| |
| final TypeProvider _typeProvider; |
| |
| final UriConverter _uriConverter; |
| |
| final ContextRoot _contextRoot; |
| |
| _AnalyzerDriver._( |
| this._session, this._typeProvider, this._uriConverter, this._contextRoot); |
| |
| Future<ComparisonNode> analyzeFiles(Iterable<String> filePaths) async { |
| var libraryNodes = <ComparisonNode>[]; |
| for (var filePath in filePaths) { |
| var kind = await _session.getSourceKind(filePath); |
| if (kind == SourceKind.LIBRARY) { |
| var importUri = _uriConverter.pathToUri(filePath); |
| var libraryElement = |
| await _session.getLibraryByUri(importUri.toString()); |
| var childNodes = <ComparisonNode>[]; |
| if (libraryElement.name.isNotEmpty) { |
| childNodes.add(ComparisonNode('name=${libraryElement.name}')); |
| } |
| for (var compilationUnit in libraryElement.units) { |
| var unitResult = |
| await _session.getResolvedAst(compilationUnit.source.fullName); |
| _AnalyzerVisitor(_typeProvider, childNodes) |
| ._visitList(unitResult.unit.declarations); |
| } |
| libraryNodes |
| .add(ComparisonNode.sorted(importUri.toString(), childNodes)); |
| } |
| } |
| return ComparisonNode.sorted('Component', libraryNodes); |
| } |
| |
| Future<ComparisonNode> analyzePackage() async { |
| return analyzeFiles(_contextRoot.analyzedFiles()); |
| } |
| |
| static Future<_AnalyzerDriver> create(String startingPath) async { |
| var contextCollection = |
| AnalysisContextCollection(includedPaths: [startingPath]); |
| var contexts = contextCollection.contexts; |
| if (contexts.length != 1) { |
| throw new StateError('Expected exactly one context'); |
| } |
| var context = contexts[0]; |
| var session = context.currentSession; |
| var typeProvider = await session.typeProvider; |
| var uriConverter = session.uriConverter; |
| var contextRoot = context.contextRoot; |
| return _AnalyzerDriver._(session, typeProvider, uriConverter, contextRoot); |
| } |
| } |
| |
| /// Visitor for serializing the contents of an analyzer AST into |
| /// ComparisonNodes. |
| /// |
| /// Results are accumulated into [_resultNodes]. |
| class _AnalyzerVisitor extends UnifyingAstVisitor<void> { |
| final TypeProvider _typeProvider; |
| |
| final List<ComparisonNode> _resultNodes; |
| |
| _AnalyzerVisitor(this._typeProvider, this._resultNodes); |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._handleClassOrClassTypeAlias(node.declaredElement); |
| visitor._visitList(node.members); |
| _resultNodes |
| .add(ComparisonNode.sorted('Class ${node.name.name}', children)); |
| } |
| |
| @override |
| void visitClassTypeAlias(ClassTypeAlias node) { |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._handleClassOrClassTypeAlias(node.declaredElement); |
| _resultNodes.add( |
| ComparisonNode.sorted('MixinApplication ${node.name.name}', children)); |
| } |
| |
| @override |
| void visitConstructorDeclaration(ConstructorDeclaration node) { |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._visitParameters(node.parameters); |
| _resultNodes.add(ComparisonNode.sorted( |
| 'Constructor ${node.name?.name ?? '(unnamed)'}', children)); |
| } |
| |
| @override |
| void visitEnumDeclaration(EnumDeclaration node) { |
| var children = <ComparisonNode>[]; |
| for (var enumValue in node.constants) { |
| children.add(ComparisonNode('EnumValue ${enumValue.name.name}')); |
| } |
| _resultNodes.add(ComparisonNode.sorted('Enum ${node.name.name}', children)); |
| } |
| |
| @override |
| void visitFieldDeclaration(FieldDeclaration node) { |
| node.fields.accept(this); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| String kind; |
| if (node.isGetter) { |
| kind = 'Getter'; |
| } else if (node.isSetter) { |
| kind = 'Setter'; |
| } else { |
| // Kernel calls top level functions "methods". |
| kind = 'Method'; |
| } |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._visitTypeParameters(node.declaredElement.typeParameters); |
| visitor._visitParameters(node.functionExpression.parameters); |
| children |
| .add(_translateType('Return type: ', node.declaredElement.returnType)); |
| _resultNodes |
| .add(ComparisonNode.sorted('$kind ${node.name.name}', children)); |
| } |
| |
| @override |
| void visitFunctionTypeAlias(FunctionTypeAlias node) { |
| _visitTypedef(node); |
| } |
| |
| @override |
| void visitGenericTypeAlias(GenericTypeAlias node) { |
| _visitTypedef(node); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| var name = node.name.name; |
| String kind; |
| if (node.isGetter) { |
| kind = 'Getter'; |
| } else if (node.isSetter) { |
| kind = 'Setter'; |
| } else if (node.isOperator) { |
| kind = 'Operator'; |
| if (name == '-' && node.declaredElement.parameters.isEmpty) { |
| name = 'unary-'; |
| } |
| } else { |
| kind = 'Method'; |
| } |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._visitTypeParameters(node.declaredElement.typeParameters); |
| visitor._visitParameters(node.parameters); |
| children |
| .add(_translateType('Return type: ', node.declaredElement.returnType)); |
| _resultNodes.add(ComparisonNode.sorted('$kind $name', children)); |
| } |
| |
| @override |
| void visitMixinDeclaration(MixinDeclaration node) { |
| // At present, kernel doesn't distinguish between mixin and class |
| // declarations. So treat the mixin as a class. |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._handleClassOrClassTypeAlias(node.declaredElement); |
| visitor._visitList(node.members); |
| _resultNodes |
| .add(ComparisonNode.sorted('Mixin ${node.name.name}', children)); |
| } |
| |
| @override |
| Null visitNode(AstNode node) { |
| throw new UnimplementedError('AnalyzerVisitor: ${node.runtimeType}'); |
| } |
| |
| @override |
| void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| node.variables.accept(this); |
| } |
| |
| @override |
| void visitVariableDeclarationList(VariableDeclarationList node) { |
| for (var variableDeclaration in node.variables) { |
| var children = <ComparisonNode>[]; |
| children.add( |
| _translateType('Type: ', variableDeclaration.declaredElement.type)); |
| // Kernel calls both fields and top level variable declarations "fields". |
| _resultNodes.add(ComparisonNode.sorted( |
| 'Field ${variableDeclaration.name.name}', children)); |
| } |
| } |
| |
| void _handleClassOrClassTypeAlias(ClassElement element) { |
| _visitTypeParameters(element.typeParameters); |
| if (element.isMixin) { |
| for (int i = 0; i < element.superclassConstraints.length; i++) { |
| _resultNodes |
| .add(_translateType('On $i: ', element.superclassConstraints[i])); |
| } |
| } else { |
| if (element.supertype != null) { |
| _resultNodes.add(_translateType('Extends: ', element.supertype)); |
| } |
| for (int i = 0; i < element.mixins.length; i++) { |
| _resultNodes.add(_translateType('Mixin $i: ', element.mixins[i])); |
| } |
| } |
| for (int i = 0; i < element.interfaces.length; i++) { |
| _resultNodes |
| .add(_translateType('Implements $i: ', element.interfaces[i])); |
| } |
| } |
| |
| /// Converts the analyzer representation of a type into a ComparisonNode. |
| ComparisonNode _translateType(String prefix, DartType type) { |
| if (type is InterfaceType) { |
| var children = <ComparisonNode>[]; |
| children |
| .add(ComparisonNode('Library: ${type.element.librarySource.uri}')); |
| for (int i = 0; i < type.typeArguments.length; i++) { |
| children.add(_translateType('Type arg $i: ', type.typeArguments[i])); |
| } |
| return ComparisonNode('${prefix}InterfaceType ${type.name}', children); |
| } |
| if (type is TypeParameterType) { |
| // TODO(paulberry): disambiguate if needed. |
| return ComparisonNode('${prefix}TypeParameterType: ${type.name}'); |
| } |
| if (type.isDynamic) { |
| return ComparisonNode('${prefix}Dynamic'); |
| } |
| if (type.isVoid) { |
| return ComparisonNode('${prefix}Void'); |
| } |
| if (type is FunctionType) { |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| visitor._visitTypeParameters(type.typeFormals); |
| children.add(_translateType('Return type: ', type.returnType)); |
| int positionalParameterIndex = 0; |
| for (var parameterElement in type.parameters) { |
| var kind = parameterElement.isNotOptional |
| ? 'Required' |
| : parameterElement.isOptionalPositional ? 'Optional' : 'Named'; |
| var name = parameterElement.isNamed |
| ? parameterElement.name |
| : '${positionalParameterIndex++}'; |
| children.add( |
| _translateType('$kind parameter $name: ', parameterElement.type)); |
| } |
| return ComparisonNode.sorted('${prefix}FunctionType', children); |
| } |
| throw new UnimplementedError('_translateType: ${type.runtimeType}'); |
| } |
| |
| /// Visits all the nodes in [nodes]. |
| void _visitList(List<AstNode> nodes) { |
| for (var astNode in nodes) { |
| astNode.accept(this); |
| } |
| } |
| |
| void _visitParameters(FormalParameterList parameters) { |
| var children = <ComparisonNode>[]; |
| // Note: parameters == null for getters |
| if (parameters != null) { |
| for (var parameter in parameters.parameters) { |
| var element = parameter.declaredElement; |
| var kind = element.isNotOptional |
| ? 'Required' |
| : element.isOptionalPositional ? 'Optional' : 'Named'; |
| var parameterChildren = <ComparisonNode>[]; |
| parameterChildren.add(_translateType('Type: ', element.type)); |
| children.add( |
| ComparisonNode.sorted('$kind: ${element.name}', parameterChildren)); |
| } |
| } |
| _resultNodes.add(ComparisonNode('Parameters', children)); |
| } |
| |
| void _visitTypedef(TypeAlias node) { |
| var children = <ComparisonNode>[]; |
| var visitor = _AnalyzerVisitor(_typeProvider, children); |
| GenericTypeAliasElement element = node.declaredElement; |
| visitor._visitTypeParameters(element.typeParameters); |
| children.add(_translateType('Type: ', element.function.type)); |
| _resultNodes |
| .add(ComparisonNode.sorted('Typedef ${node.name.name}', children)); |
| } |
| |
| void _visitTypeParameters(List<TypeParameterElement> typeParameters) { |
| for (int i = 0; i < typeParameters.length; i++) { |
| _resultNodes.add(ComparisonNode( |
| 'Type parameter $i: ${typeParameters[i].name}', [ |
| _translateType( |
| 'Bound: ', typeParameters[i].bound ?? _typeProvider.objectType) |
| ])); |
| } |
| } |
| } |