blob: 7abd5b87677b1ee93c833a5bc8e13739ea326418 [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/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
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.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);
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)
]));
}
}
}