e9d8109258
Allow enums, extensions, and mixins to use `;` as their body and represent that form explicitly in the AST. The parser now produces `EmptyEnumBody` or `EmptyClassBody` for the empty form. Also replace `LibraryIdentifier` with token-based `DottedName` and store the full token sequence for dotted names. This preserves periods and source offsets directly in the AST, which keeps printing, selection, and directive name handling working with the new shape. See https://github.com/dart-lang/sdk/issues/62819 See https://github.com/dart-lang/language/issues/4645 Google3 presubmit looks green: https://fusion2.corp.google.com/presubmit/884063020/OCL:884063020:BASE:884079610:1773618867493:b2110d76 Change-Id: I2d023cd03b6423da634c3e14742e02a61dc3b403 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/486080 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
1419 lines
41 KiB
Dart
1419 lines
41 KiB
Dart
// Copyright (c) 2020, 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:io' as io;
|
|
|
|
import 'package:analysis_server/src/utilities/extensions/ast.dart';
|
|
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
|
import 'package:analyzer/dart/analysis/context_root.dart';
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import 'package:analyzer/dart/ast/visitor.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
|
|
import 'package:analyzer/src/utilities/extensions/diagnostic.dart';
|
|
import 'package:analyzer/src/utilities/extensions/object.dart';
|
|
import 'package:args/args.dart';
|
|
|
|
/// Compute and print lexical and semantic information about a package.
|
|
Future<void> main(List<String> args) async {
|
|
var parser = createArgParser();
|
|
var result = parser.parse(args);
|
|
|
|
if (validArguments(parser, result)) {
|
|
var out = io.stdout;
|
|
var rootPath = result.rest[0];
|
|
out.writeln('Analyzing root: "$rootPath"');
|
|
|
|
var computer = CodeShapeMetricsComputer();
|
|
var stopwatch = Stopwatch();
|
|
stopwatch.start();
|
|
await computer.compute(rootPath);
|
|
stopwatch.stop();
|
|
var duration = Duration(milliseconds: stopwatch.elapsedMilliseconds);
|
|
out.writeln(' Analysis performed in $duration');
|
|
computer.writeResults(out);
|
|
await out.flush();
|
|
}
|
|
io.exit(0);
|
|
}
|
|
|
|
/// Create a parser that can be used to parse the command-line arguments.
|
|
ArgParser createArgParser() {
|
|
var parser = ArgParser();
|
|
parser.addOption('help', abbr: 'h', help: 'Print this help message.');
|
|
return parser;
|
|
}
|
|
|
|
/// Print usage information for this tool.
|
|
void printUsage(ArgParser parser, {String? error}) {
|
|
if (error != null) {
|
|
print(error);
|
|
print('');
|
|
}
|
|
print('usage: dart code_metrics.dart [options] packagePath');
|
|
print('');
|
|
print('Compute and print lexical and semantic information about a package.');
|
|
print('');
|
|
print(parser.usage);
|
|
}
|
|
|
|
/// Return `true` if the command-line arguments (represented by the [result] and
|
|
/// parsed by the [parser]) are valid.
|
|
bool validArguments(ArgParser parser, ArgResults result) {
|
|
if (result.wasParsed('help')) {
|
|
printUsage(parser);
|
|
return false;
|
|
} else if (result.rest.length != 1) {
|
|
printUsage(parser, error: 'No package path specified.');
|
|
return false;
|
|
}
|
|
var rootPath = result.rest[0];
|
|
if (!io.Directory(rootPath).existsSync()) {
|
|
printUsage(parser, error: 'The directory "$rootPath" does not exist.');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// An object that records the data used to compute the metrics.
|
|
class CodeShapeData {
|
|
/// A table mapping the name of a node class to the number of instances of
|
|
/// that class that were visited.
|
|
Map<String, int> nodeData = {};
|
|
|
|
/// A table mapping the name of a node class and the name of the node's
|
|
/// parent's class to the number of instances of the node class that had a
|
|
/// parent of the parent class.
|
|
Map<String, Map<String, int>> parentData = {};
|
|
|
|
/// A table mapping the name of a node class, the name of a property of that
|
|
/// node, and the class of the property's value to number of instances of the
|
|
/// node class that had a value of the property class for the property.
|
|
Map<String, Map<String, Map<String, int>>> childData = {};
|
|
|
|
/// A table mapping the name of a node class, the name of a list-valued
|
|
/// property of that node, and the length of the list to number of instances
|
|
/// of the node class that had a value of that length for the property.
|
|
Map<String, Map<String, Map<int, int>>> lengthData = {};
|
|
|
|
/// Information about children of nodes that were not correctly recorded.
|
|
Set<String> missedChildren = {};
|
|
|
|
/// Initialize a newly created set of relevance data to be empty.
|
|
CodeShapeData();
|
|
|
|
/// Record that an element of the given [node] was found in the given
|
|
/// [context].
|
|
void recordNode(String nodeClassName, String property, AstNode node) {
|
|
var childClass = node.runtimeType.toString();
|
|
if (childClass.endsWith('Impl')) {
|
|
childClass = childClass.substring(0, childClass.length - 4);
|
|
}
|
|
_recordChildData(nodeClassName, property, childClass);
|
|
}
|
|
|
|
/// Record that a node that is an instance of the [nodeClassName] was visited.
|
|
void recordNodeClass(String nodeClassName) {
|
|
nodeData[nodeClassName] = (nodeData[nodeClassName] ?? 0) + 1;
|
|
}
|
|
|
|
void recordNodeList(String nodeClassName, String property, NodeList list) {
|
|
_recordListLength(nodeClassName, property, list);
|
|
for (var element in list) {
|
|
recordNode(nodeClassName, property, element);
|
|
}
|
|
}
|
|
|
|
void recordParentClass(String nodeClassName, String parentClassName) {
|
|
var classMap = parentData.putIfAbsent(nodeClassName, () => {});
|
|
classMap[parentClassName] = (classMap[parentClassName] ?? 0) + 1;
|
|
}
|
|
|
|
void recordToken(String nodeClassName, String property, Token? token) {
|
|
var lexeme = token?.lexeme ?? 'null';
|
|
_recordChildData(nodeClassName, property, lexeme);
|
|
}
|
|
|
|
void _recordChildData(
|
|
String nodeClassName,
|
|
String property,
|
|
String childValue,
|
|
) {
|
|
var classMap = childData.putIfAbsent(nodeClassName, () => {});
|
|
var propertyMap = classMap.putIfAbsent(property, () => {});
|
|
propertyMap[childValue] = (propertyMap[childValue] ?? 0) + 1;
|
|
}
|
|
|
|
/// Record that an element of the given [node] was found in the given
|
|
/// [context].
|
|
void _recordListLength(String nodeClassName, String property, NodeList list) {
|
|
var classMap = lengthData.putIfAbsent(nodeClassName, () => {});
|
|
var propertyMap = classMap.putIfAbsent(property, () => {});
|
|
var length = list.length;
|
|
propertyMap[length] = (propertyMap[length] ?? 0) + 1;
|
|
}
|
|
}
|
|
|
|
/// An object that visits a compilation unit in order to record the data used to
|
|
/// compute the metrics.
|
|
class CodeShapeDataCollector extends RecursiveAstVisitor<void> {
|
|
/// The data being collected.
|
|
final CodeShapeData data;
|
|
|
|
/// Initialize a newly created collector to add data points to the given
|
|
/// [data].
|
|
CodeShapeDataCollector(this.data);
|
|
|
|
@override
|
|
void visitAdjacentStrings(AdjacentStrings node) {
|
|
_visitChildren(node, {'strings': node.strings});
|
|
super.visitAdjacentStrings(node);
|
|
}
|
|
|
|
@override
|
|
void visitAnnotation(Annotation node) {
|
|
_visitChildren(node, {
|
|
'name': node.name,
|
|
'constructorName': node.constructorName,
|
|
'arguments': node.arguments,
|
|
});
|
|
super.visitAnnotation(node);
|
|
}
|
|
|
|
@override
|
|
void visitArgumentList(ArgumentList node) {
|
|
_visitChildren(node, {'arguments': node.arguments});
|
|
super.visitArgumentList(node);
|
|
}
|
|
|
|
@override
|
|
void visitAsExpression(AsExpression node) {
|
|
_visitChildren(node, {'expression': node.expression, 'type': node.type});
|
|
super.visitAsExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssertInitializer(AssertInitializer node) {
|
|
_visitChildren(node, {
|
|
'condition': node.condition,
|
|
'message': node.message,
|
|
});
|
|
super.visitAssertInitializer(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssertStatement(AssertStatement node) {
|
|
_visitChildren(node, {
|
|
'condition': node.condition,
|
|
'message': node.message,
|
|
});
|
|
super.visitAssertStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssignmentExpression(AssignmentExpression node) {
|
|
_visitChildren(node, {
|
|
'leftHandSide': node.leftHandSide,
|
|
'operator': node.operator,
|
|
'rightHandSide': node.rightHandSide,
|
|
});
|
|
super.visitAssignmentExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitAwaitExpression(AwaitExpression node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitAwaitExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitBinaryExpression(BinaryExpression node) {
|
|
_visitChildren(node, {
|
|
'leftOperand': node.leftOperand,
|
|
'operator': node.operator,
|
|
'rightOperand': node.rightOperand,
|
|
});
|
|
super.visitBinaryExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitBlock(Block node) {
|
|
_visitChildren(node, {'statement': node.statements});
|
|
super.visitBlock(node);
|
|
}
|
|
|
|
@override
|
|
void visitBlockFunctionBody(BlockFunctionBody node) {
|
|
_visitChildren(node, {
|
|
'keyword': node.keyword,
|
|
'star': node.star,
|
|
'block': node.block,
|
|
});
|
|
super.visitBlockFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitBooleanLiteral(BooleanLiteral node) {
|
|
_visitChildren(node, {'literal': node.literal});
|
|
super.visitBooleanLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitBreakStatement(BreakStatement node) {
|
|
_visitChildren(node, {'label': node.label});
|
|
super.visitBreakStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitCascadeExpression(CascadeExpression node) {
|
|
_visitChildren(node, {
|
|
'target': node.target,
|
|
'cascadeSections': node.cascadeSections,
|
|
});
|
|
super.visitCascadeExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitCatchClause(CatchClause node) {
|
|
_visitChildren(node, {
|
|
'exceptionType': node.exceptionType,
|
|
'exceptionParameter': node.exceptionParameter,
|
|
'stackTraceParameter': node.stackTraceParameter,
|
|
'body': node.body,
|
|
});
|
|
super.visitCatchClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassDeclaration(ClassDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'abstractKeyword': node.abstractKeyword,
|
|
'name': node.namePart.typeName,
|
|
'typeParameters': node.namePart.typeParameters,
|
|
'extendsClause': node.extendsClause,
|
|
'withClause': node.withClause,
|
|
'implementsClause': node.implementsClause,
|
|
'nativeClause': node.nativeClause,
|
|
'members': node.members2,
|
|
});
|
|
super.visitClassDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassTypeAlias(ClassTypeAlias node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'abstractKeyword': node.abstractKeyword,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'superclass': node.superclass,
|
|
'withClause': node.withClause,
|
|
'implementsClause': node.implementsClause,
|
|
});
|
|
super.visitClassTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitComment(Comment node) {
|
|
_visitChildren(node, {'references': node.references});
|
|
super.visitComment(node);
|
|
}
|
|
|
|
@override
|
|
void visitCommentReference(CommentReference node) {
|
|
_visitChildren(node, {
|
|
'newKeyword': node.newKeyword,
|
|
'expression': node.expression,
|
|
});
|
|
super.visitCommentReference(node);
|
|
}
|
|
|
|
@override
|
|
void visitCompilationUnit(CompilationUnit node) {
|
|
_visitChildren(node, {
|
|
'scriptTag': node.scriptTag,
|
|
'directives': node.directives,
|
|
'declarations': node.declarations,
|
|
});
|
|
super.visitCompilationUnit(node);
|
|
}
|
|
|
|
@override
|
|
void visitConditionalExpression(ConditionalExpression node) {
|
|
_visitChildren(node, {
|
|
'condition': node.condition,
|
|
'thenExpression': node.thenExpression,
|
|
'elseExpression': node.elseExpression,
|
|
});
|
|
super.visitConditionalExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitConfiguration(Configuration node) {
|
|
_visitChildren(node, {
|
|
'name': node.name,
|
|
'value': node.value,
|
|
'uri': node.uri,
|
|
});
|
|
super.visitConfiguration(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'externalKeyword': node.externalKeyword,
|
|
'constKeyword': node.constKeyword,
|
|
'factoryKeyword': node.factoryKeyword,
|
|
// TODO(scheglov): support primary constructors
|
|
'returnType': node.typeName!,
|
|
'name': node.name,
|
|
'parameters': node.parameters,
|
|
'initializers': node.initializers,
|
|
'redirectedConstructor': node.redirectedConstructor,
|
|
'body': node.body,
|
|
});
|
|
super.visitConstructorDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
|
|
_visitChildren(node, {
|
|
'thisKeyword': node.thisKeyword,
|
|
'fieldName': node.fieldName,
|
|
'expression': node.expression,
|
|
});
|
|
super.visitConstructorFieldInitializer(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorName(ConstructorName node) {
|
|
_visitChildren(node, {'type': node.type, 'name': node.name});
|
|
super.visitConstructorName(node);
|
|
}
|
|
|
|
@override
|
|
void visitContinueStatement(ContinueStatement node) {
|
|
_visitChildren(node, {'label': node.label});
|
|
super.visitContinueStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitDeclaredIdentifier(DeclaredIdentifier node) {
|
|
_visitChildren(node, {
|
|
'keyword': node.keyword,
|
|
'type': node.type,
|
|
'name': node.name,
|
|
});
|
|
super.visitDeclaredIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitDefaultFormalParameter(DefaultFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'parameter': node.parameter,
|
|
'defaultValue': node.defaultValue,
|
|
});
|
|
super.visitDefaultFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitDoStatement(DoStatement node) {
|
|
_visitChildren(node, {'body': node.body, 'condition': node.condition});
|
|
super.visitDoStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitDottedName(DottedName node) {
|
|
_visitChildren(node, {});
|
|
super.visitDottedName(node);
|
|
}
|
|
|
|
@override
|
|
void visitDoubleLiteral(DoubleLiteral node) {
|
|
_visitChildren(node, {});
|
|
super.visitDoubleLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitEmptyFunctionBody(EmptyFunctionBody node) {
|
|
_visitChildren(node, {});
|
|
super.visitEmptyFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitEmptyStatement(EmptyStatement node) {
|
|
_visitChildren(node, {});
|
|
super.visitEmptyStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.name,
|
|
});
|
|
super.visitEnumConstantDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitEnumDeclaration(EnumDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.namePart.typeName,
|
|
'constants': node.body.tryCast<BlockEnumBody>()?.constants,
|
|
});
|
|
super.visitEnumDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitExportDirective(ExportDirective node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'uri': node.uri,
|
|
'configurations': node.configurations,
|
|
'combinators': node.combinators,
|
|
});
|
|
super.visitExportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
|
_visitChildren(node, {
|
|
'keyword': node.keyword,
|
|
'star': node.star,
|
|
'expression': node.expression,
|
|
});
|
|
super.visitExpressionFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitExpressionStatement(ExpressionStatement node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitExpressionStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtendsClause(ExtendsClause node) {
|
|
_visitChildren(node, {'superclass': node.superclass});
|
|
super.visitExtendsClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
|
_visitChildren(node, {
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'onClause': node.onClause,
|
|
'member': node.body.tryCast<BlockClassBody>()?.members,
|
|
});
|
|
super.visitExtensionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionOverride(ExtensionOverride node) {
|
|
_visitChildren(node, {
|
|
'importPrefix': node.importPrefix,
|
|
'name': node.name,
|
|
'typeArguments': node.typeArguments,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitExtensionOverride(node);
|
|
}
|
|
|
|
@override
|
|
void visitFieldDeclaration(FieldDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'abstractKeyword': node.abstractKeyword,
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'externalKeyword': node.externalKeyword,
|
|
'staticKeyword': node.staticKeyword,
|
|
'fields': node.fields,
|
|
});
|
|
super.visitFieldDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFieldFormalParameter(FieldFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'keyword': node.keyword,
|
|
'type': node.type,
|
|
'thisKeyword': node.thisKeyword,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
'question': node.question,
|
|
});
|
|
super.visitFieldFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
|
|
_visitChildren(node, {
|
|
'loopVariable': node.loopVariable,
|
|
'iterable': node.iterable,
|
|
});
|
|
super.visitForEachPartsWithDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
|
|
_visitChildren(node, {
|
|
'identifier': node.identifier,
|
|
'iterable': node.iterable,
|
|
});
|
|
super.visitForEachPartsWithIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitForElement(ForElement node) {
|
|
_visitChildren(node, {
|
|
'awaitKeyword': node.awaitKeyword,
|
|
'forLoopParts': node.forLoopParts,
|
|
'body': node.body,
|
|
});
|
|
super.visitForElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitFormalParameterList(FormalParameterList node) {
|
|
_visitChildren(node, {'parameters': node.parameters});
|
|
super.visitFormalParameterList(node);
|
|
}
|
|
|
|
@override
|
|
void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
|
|
_visitChildren(node, {
|
|
'variables': node.variables,
|
|
'condition': node.condition,
|
|
'updater': node.updaters,
|
|
});
|
|
super.visitForPartsWithDeclarations(node);
|
|
}
|
|
|
|
@override
|
|
void visitForPartsWithExpression(ForPartsWithExpression node) {
|
|
_visitChildren(node, {
|
|
'initialization': node.initialization,
|
|
'condition': node.condition,
|
|
'updater': node.updaters,
|
|
});
|
|
super.visitForPartsWithExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitForStatement(ForStatement node) {
|
|
_visitChildren(node, {
|
|
'awaitKeyword': node.awaitKeyword,
|
|
'forLoopParts': node.forLoopParts,
|
|
'body': node.body,
|
|
});
|
|
super.visitForStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclaration(FunctionDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'externalKeyword': node.externalKeyword,
|
|
'propertyKeyword': node.propertyKeyword,
|
|
'name': node.name,
|
|
'functionExpression': node.functionExpression,
|
|
'returnType': node.returnType,
|
|
});
|
|
super.visitFunctionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
|
|
_visitChildren(node, {'functionDeclaration': node.functionDeclaration});
|
|
super.visitFunctionDeclarationStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionExpression(FunctionExpression node) {
|
|
_visitChildren(node, {
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
'body': node.body,
|
|
});
|
|
super.visitFunctionExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
|
_visitChildren(node, {
|
|
'function': node.function,
|
|
'typeArguments': node.typeArguments,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitFunctionExpressionInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionTypeAlias(FunctionTypeAlias node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'returnType': node.returnType,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
});
|
|
super.visitFunctionTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'returnType': node.returnType,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
'question': node.question,
|
|
});
|
|
super.visitFunctionTypedFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitGenericFunctionType(GenericFunctionType node) {
|
|
_visitChildren(node, {
|
|
'returnType': node.returnType,
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
'question': node.question,
|
|
});
|
|
super.visitGenericFunctionType(node);
|
|
}
|
|
|
|
@override
|
|
void visitGenericTypeAlias(GenericTypeAlias node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'functionType': node.functionType,
|
|
});
|
|
super.visitGenericTypeAlias(node);
|
|
}
|
|
|
|
@override
|
|
void visitHideCombinator(HideCombinator node) {
|
|
_visitChildren(node, {'hiddenNames': node.hiddenNames});
|
|
super.visitHideCombinator(node);
|
|
}
|
|
|
|
@override
|
|
void visitIfElement(IfElement node) {
|
|
_visitChildren(node, {
|
|
'condition': node.expression,
|
|
'thenElement': node.thenElement,
|
|
'elseElement': node.elseElement,
|
|
});
|
|
super.visitIfElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitIfStatement(IfStatement node) {
|
|
_visitChildren(node, {
|
|
'condition': node.expression,
|
|
'thenStatement': node.thenStatement,
|
|
'elseStatement': node.elseStatement,
|
|
});
|
|
super.visitIfStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitImplementsClause(ImplementsClause node) {
|
|
_visitChildren(node, {'interfaces': node.interfaces});
|
|
super.visitImplementsClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitImportDirective(ImportDirective node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'uri': node.uri,
|
|
'deferredKeyword': node.deferredKeyword,
|
|
'asKeyword': node.asKeyword,
|
|
'prefix': node.prefix,
|
|
'configurations': node.configurations,
|
|
'combinators': node.combinators,
|
|
});
|
|
super.visitImportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitIndexExpression(IndexExpression node) {
|
|
_visitChildren(node, {'target': node.target, 'index': node.index});
|
|
super.visitIndexExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceCreationExpression(InstanceCreationExpression node) {
|
|
_visitChildren(node, {
|
|
'keyword': node.keyword,
|
|
'constructorName': node.constructorName,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitInstanceCreationExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitIntegerLiteral(IntegerLiteral node) {
|
|
var value = node.value;
|
|
if (value == -1 || value == 0 || value == 1) {
|
|
_visitChildren(node, {'literal': node.literal});
|
|
} else {
|
|
_visitChildren(node, {});
|
|
}
|
|
super.visitIntegerLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitInterpolationExpression(InterpolationExpression node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitInterpolationExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitInterpolationString(InterpolationString node) {
|
|
_visitChildren(node, {});
|
|
super.visitInterpolationString(node);
|
|
}
|
|
|
|
@override
|
|
void visitIsExpression(IsExpression node) {
|
|
_visitChildren(node, {'expression': node.expression, 'type': node.type});
|
|
super.visitIsExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitLabel(Label node) {
|
|
_visitChildren(node, {'label': node.label});
|
|
super.visitLabel(node);
|
|
}
|
|
|
|
@override
|
|
void visitLabeledStatement(LabeledStatement node) {
|
|
_visitChildren(node, {'statement': node.statement});
|
|
super.visitLabeledStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitLibraryDirective(LibraryDirective node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.name,
|
|
});
|
|
super.visitLibraryDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitListLiteral(ListLiteral node) {
|
|
_visitChildren(node, {
|
|
'typeArguments': node.typeArguments,
|
|
'elements': node.elements,
|
|
});
|
|
super.visitListLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitMapLiteralEntry(MapLiteralEntry node) {
|
|
_visitChildren(node, {'key': node.key, 'value': node.value});
|
|
super.visitMapLiteralEntry(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodDeclaration(MethodDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'externalKeyword': node.externalKeyword,
|
|
'modifierKeyword': node.modifierKeyword,
|
|
'returnType': node.returnType,
|
|
'name': node.name,
|
|
'operatorKeyword': node.operatorKeyword,
|
|
'typeParameters': node.typeParameters,
|
|
'parameters': node.parameters,
|
|
'body': node.body,
|
|
});
|
|
super.visitMethodDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodInvocation(MethodInvocation node) {
|
|
_visitChildren(node, {
|
|
'target': node.target,
|
|
'methodName': node.methodName,
|
|
'typeArguments': node.typeArguments,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitMethodInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinDeclaration(MixinDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'onClause': node.onClause,
|
|
'implementsClause': node.implementsClause,
|
|
'members': node.body.tryCast<BlockClassBody>()?.members,
|
|
});
|
|
super.visitMixinDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinOnClause(MixinOnClause node) {
|
|
_visitChildren(node, {'superclassConstraints': node.superclassConstraints});
|
|
super.visitMixinOnClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitNamedExpression(NamedExpression node) {
|
|
_visitChildren(node, {'name': node.name, 'expression': node.expression});
|
|
super.visitNamedExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitNamedType(NamedType node) {
|
|
_visitChildren(node, {
|
|
'importPrefix': node.importPrefix,
|
|
'name': node.name,
|
|
'typeArguments': node.typeArguments,
|
|
'question': node.question,
|
|
});
|
|
super.visitNamedType(node);
|
|
}
|
|
|
|
@override
|
|
void visitNativeClause(NativeClause node) {
|
|
_visitChildren(node, {'name': node.name});
|
|
super.visitNativeClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitNativeFunctionBody(NativeFunctionBody node) {
|
|
_visitChildren(node, {});
|
|
super.visitNativeFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitNullLiteral(NullLiteral node) {
|
|
_visitChildren(node, {});
|
|
super.visitNullLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitParenthesizedExpression(ParenthesizedExpression node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitParenthesizedExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartDirective(PartDirective node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'uri': node.uri,
|
|
});
|
|
super.visitPartDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartOfDirective(PartOfDirective node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'libraryName': node.libraryName,
|
|
'uri': node.uri,
|
|
});
|
|
super.visitPartOfDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitPostfixExpression(PostfixExpression node) {
|
|
_visitChildren(node, {'operand': node.operand, 'operator': node.operator});
|
|
super.visitPostfixExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitPrefixedIdentifier(PrefixedIdentifier node) {
|
|
_visitChildren(node, {
|
|
'prefix': node.prefix,
|
|
'identifier': node.identifier,
|
|
});
|
|
super.visitPrefixedIdentifier(node);
|
|
}
|
|
|
|
@override
|
|
void visitPrefixExpression(PrefixExpression node) {
|
|
_visitChildren(node, {'operator': node.operator, 'operand': node.operand});
|
|
super.visitPrefixExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitPropertyAccess(PropertyAccess node) {
|
|
_visitChildren(node, {
|
|
'target': node.target,
|
|
'propertyName': node.propertyName,
|
|
});
|
|
super.visitPropertyAccess(node);
|
|
}
|
|
|
|
@override
|
|
void visitRedirectingConstructorInvocation(
|
|
RedirectingConstructorInvocation node,
|
|
) {
|
|
_visitChildren(node, {
|
|
'constructorName': node.constructorName,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitRedirectingConstructorInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitRethrowExpression(RethrowExpression node) {
|
|
_visitChildren(node, {});
|
|
super.visitRethrowExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitReturnStatement(ReturnStatement node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitReturnStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitScriptTag(ScriptTag node) {
|
|
_visitChildren(node, {});
|
|
super.visitScriptTag(node);
|
|
}
|
|
|
|
@override
|
|
void visitSetOrMapLiteral(SetOrMapLiteral node) {
|
|
_visitChildren(node, {
|
|
'typeArguments': node.typeArguments,
|
|
'elements': node.elements,
|
|
});
|
|
super.visitSetOrMapLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitShowCombinator(ShowCombinator node) {
|
|
_visitChildren(node, {'shownNames': node.shownNames});
|
|
super.visitShowCombinator(node);
|
|
}
|
|
|
|
@override
|
|
void visitSimpleFormalParameter(SimpleFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'keyword': node.keyword,
|
|
'type': node.type,
|
|
'name': node.name,
|
|
});
|
|
super.visitSimpleFormalParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitSimpleStringLiteral(SimpleStringLiteral node) {
|
|
_visitChildren(node, {});
|
|
super.visitSimpleStringLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitSpreadElement(SpreadElement node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitSpreadElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitStringInterpolation(StringInterpolation node) {
|
|
_visitChildren(node, {'elements': node.elements});
|
|
super.visitStringInterpolation(node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
|
_visitChildren(node, {
|
|
'constructorName': node.constructorName,
|
|
'argumentList': node.argumentList,
|
|
});
|
|
super.visitSuperConstructorInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperExpression(SuperExpression node) {
|
|
_visitChildren(node, {});
|
|
super.visitSuperExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchCase(SwitchCase node) {
|
|
_visitChildren(node, {
|
|
'labels': node.labels,
|
|
'expression': node.expression,
|
|
'statements': node.statements,
|
|
});
|
|
super.visitSwitchCase(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchDefault(SwitchDefault node) {
|
|
_visitChildren(node, {'statements': node.statements});
|
|
super.visitSwitchDefault(node);
|
|
}
|
|
|
|
@override
|
|
void visitSwitchStatement(SwitchStatement node) {
|
|
_visitChildren(node, {
|
|
'expression': node.expression,
|
|
'members': node.members,
|
|
});
|
|
super.visitSwitchStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitSymbolLiteral(SymbolLiteral node) {
|
|
_visitChildren(node, {});
|
|
super.visitSymbolLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitThisExpression(ThisExpression node) {
|
|
_visitChildren(node, {});
|
|
super.visitThisExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitThrowExpression(ThrowExpression node) {
|
|
_visitChildren(node, {'expression': node.expression});
|
|
super.visitThrowExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'externalKeyword': node.externalKeyword,
|
|
'variables': node.variables,
|
|
});
|
|
super.visitTopLevelVariableDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitTryStatement(TryStatement node) {
|
|
_visitChildren(node, {
|
|
'body': node.body,
|
|
'catchClauses': node.catchClauses,
|
|
'finallyBlock': node.finallyBlock,
|
|
});
|
|
super.visitTryStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitTypeArgumentList(TypeArgumentList node) {
|
|
_visitChildren(node, {'arguments': node.arguments});
|
|
super.visitTypeArgumentList(node);
|
|
}
|
|
|
|
@override
|
|
void visitTypeParameter(TypeParameter node) {
|
|
_visitChildren(node, {'name': node.name, 'bound': node.bound});
|
|
super.visitTypeParameter(node);
|
|
}
|
|
|
|
@override
|
|
void visitTypeParameterList(TypeParameterList node) {
|
|
_visitChildren(node, {'typeParameters': node.typeParameters});
|
|
super.visitTypeParameterList(node);
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclaration(VariableDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'name': node.name,
|
|
'initializer': node.initializer,
|
|
});
|
|
super.visitVariableDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclarationList(VariableDeclarationList node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'type': node.type,
|
|
'variables': node.variables,
|
|
});
|
|
super.visitVariableDeclarationList(node);
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
|
|
_visitChildren(node, {'variables': node.variables});
|
|
super.visitVariableDeclarationStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitWhileStatement(WhileStatement node) {
|
|
_visitChildren(node, {'condition': node.condition, 'body': node.body});
|
|
super.visitWhileStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitWithClause(WithClause node) {
|
|
_visitChildren(node, {'mixinTypes': node.mixinTypes});
|
|
super.visitWithClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitYieldStatement(YieldStatement node) {
|
|
_visitChildren(node, {'star': node.star, 'expression': node.expression});
|
|
super.visitYieldStatement(node);
|
|
}
|
|
|
|
String _className(AstNode node) {
|
|
var className = node.runtimeType.toString();
|
|
if (className.endsWith('Impl')) {
|
|
className = className.substring(0, className.length - 4);
|
|
}
|
|
return className;
|
|
}
|
|
|
|
/// Visit the children of a node. The node is an instance of the class named
|
|
/// [parentClass] and the children are in the [childMap], keyed by the name of
|
|
/// the child property.
|
|
void _visitChildren(AstNode node, Map<String, Object?> childMap) {
|
|
var nodeClassName = _className(node);
|
|
|
|
data.recordNodeClass(nodeClassName);
|
|
|
|
var parent = node.parent;
|
|
if (parent != null) {
|
|
data.recordParentClass(nodeClassName, _className(parent));
|
|
}
|
|
|
|
var visitChildren = <AstNode>[];
|
|
for (var entry in childMap.entries) {
|
|
var property = entry.key;
|
|
var child = entry.value;
|
|
if (child is AstNode) {
|
|
data.recordNode(nodeClassName, property, child);
|
|
visitChildren.add(child);
|
|
} else if (child is NodeList) {
|
|
data.recordNodeList(nodeClassName, property, child);
|
|
visitChildren.addAll(child);
|
|
} else if (child is Token?) {
|
|
data.recordToken(nodeClassName, property, child);
|
|
} else {
|
|
throw ArgumentError('Unknown class of child: ${child.runtimeType}');
|
|
}
|
|
}
|
|
// Validate that all AST node children were included in the [childMap].
|
|
for (var entity in node.childEntities) {
|
|
if (entity is AstNode && !visitChildren.contains(entity)) {
|
|
data.missedChildren.add('$nodeClassName (${entity.runtimeType})');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An object used to compute metrics for a single file or directory.
|
|
class CodeShapeMetricsComputer {
|
|
/// The metrics data that was computed.
|
|
final CodeShapeData data = CodeShapeData();
|
|
|
|
/// Initialize a newly created metrics computer that can compute the metrics
|
|
/// in one or more files and directories.
|
|
CodeShapeMetricsComputer();
|
|
|
|
/// Compute the metrics for the file(s) in the [rootPath].
|
|
Future<void> compute(String rootPath) async {
|
|
var collection = AnalysisContextCollection(
|
|
includedPaths: [rootPath],
|
|
resourceProvider: PhysicalResourceProvider.INSTANCE,
|
|
);
|
|
var collector = CodeShapeDataCollector(data);
|
|
for (var context in collection.contexts) {
|
|
await _computeInContext(context.contextRoot, collector);
|
|
}
|
|
}
|
|
|
|
/// Write a report of the metrics that were computed to the [sink].
|
|
void writeResults(StringSink sink) {
|
|
// Write error information.
|
|
_writeMissedChildren(sink);
|
|
|
|
// Write normal information.
|
|
_writeChildData(sink);
|
|
}
|
|
|
|
/// Compute the metrics for the files in the context [root], creating a
|
|
/// separate context collection to prevent accumulating memory. The metrics
|
|
/// should be captured in the [collector]. Include additional details in the
|
|
/// output if [verbose] is `true`.
|
|
Future<void> _computeInContext(
|
|
ContextRoot root,
|
|
CodeShapeDataCollector collector,
|
|
) async {
|
|
// Create a new collection to avoid consuming large quantities of memory.
|
|
var collection = AnalysisContextCollection(
|
|
includedPaths: root.includedPaths.toList(),
|
|
excludedPaths: root.excludedPaths.toList(),
|
|
resourceProvider: PhysicalResourceProvider.INSTANCE,
|
|
);
|
|
var context = collection.contexts[0];
|
|
var pathContext = context.contextRoot.resourceProvider.pathContext;
|
|
for (var filePath in context.contextRoot.analyzedFiles()) {
|
|
if (file_paths.isDart(pathContext, filePath)) {
|
|
try {
|
|
var resolvedUnitResult = await context.currentSession.getResolvedUnit(
|
|
filePath,
|
|
);
|
|
//
|
|
// Check for errors that cause the file to be skipped.
|
|
//
|
|
if (resolvedUnitResult is! ResolvedUnitResult) {
|
|
print('File $filePath skipped because it could not be analyzed.');
|
|
print('');
|
|
continue;
|
|
} else if (resolvedUnitResult.diagnostics.errors.isNotEmpty) {
|
|
print('File $filePath skipped due to errors:');
|
|
for (var diagnostic in resolvedUnitResult.diagnostics) {
|
|
print(' ${diagnostic.toString()}');
|
|
}
|
|
print('');
|
|
continue;
|
|
}
|
|
|
|
resolvedUnitResult.unit.accept(collector);
|
|
} catch (exception) {
|
|
print('Exception caught analyzing: "$filePath"');
|
|
print(exception.toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert the contents of a single [map] into the values for each row in the
|
|
/// column occupied by the map.
|
|
List<String> _convertMap<T extends Object>(String context, Map<T, int> map) {
|
|
var columns = <String>[];
|
|
var entries = map.entries.toList()
|
|
..sort((first, second) {
|
|
return second.value.compareTo(first.value);
|
|
});
|
|
var total = 0;
|
|
for (var entry in entries) {
|
|
total += entry.value;
|
|
}
|
|
columns.add('$context ($total)');
|
|
for (var entry in entries) {
|
|
var value = entry.value;
|
|
var percent = _formatPercent(value, total);
|
|
columns.add(' $percent%: ${entry.key} ($value)');
|
|
}
|
|
return columns;
|
|
}
|
|
|
|
/// Compute and format a percentage for the fraction [value] / [total].
|
|
String _formatPercent(int value, int total) {
|
|
var percent = ((value / total) * 100).toStringAsFixed(1);
|
|
if (percent.length == 3) {
|
|
percent = ' $percent';
|
|
} else if (percent.length == 4) {
|
|
percent = ' $percent';
|
|
}
|
|
return percent;
|
|
}
|
|
|
|
/// Write the child data to the [sink].
|
|
void _writeChildData(StringSink sink) {
|
|
sink.writeln();
|
|
sink.writeln('Child data');
|
|
|
|
// TODO(brianwilkerson): This misses all node kinds for which zero instances
|
|
// were visited.
|
|
var nodeData = data.nodeData;
|
|
var parentData = data.parentData;
|
|
var childData = data.childData;
|
|
var lengthData = data.lengthData;
|
|
var nodeClasses = nodeData.keys.toList()..sort();
|
|
for (var nodeClass in nodeClasses) {
|
|
var count = nodeData[nodeClass];
|
|
sink.writeln();
|
|
sink.writeln('$nodeClass ($count)');
|
|
|
|
var parentMap = parentData[nodeClass];
|
|
if (parentMap != null) {
|
|
var lines = _convertMap('parent', parentMap);
|
|
for (var line in lines) {
|
|
sink.writeln(' $line');
|
|
}
|
|
}
|
|
|
|
var childMap = childData[nodeClass] ?? {};
|
|
var listMap = lengthData[nodeClass] ?? {};
|
|
var childNames = {...childMap.keys, ...listMap.keys}.toList()..sort();
|
|
for (var childName in childNames) {
|
|
var listLengths = listMap[childName];
|
|
if (listLengths != null) {
|
|
var lines = _convertMap(childName, listLengths);
|
|
for (var line in lines) {
|
|
sink.writeln(' $line');
|
|
}
|
|
}
|
|
|
|
var childTypes = childMap[childName];
|
|
if (childTypes != null) {
|
|
var lines = _convertMap(childName, childTypes);
|
|
for (var line in lines) {
|
|
sink.writeln(' $line');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Write a report of the metrics that were computed to the [sink].
|
|
void _writeMissedChildren(StringSink sink) {
|
|
var missedChildren = data.missedChildren;
|
|
if (missedChildren.isNotEmpty) {
|
|
sink.writeln();
|
|
sink.writeln('Missed children in the following classes:');
|
|
for (var nodeClass in missedChildren.toList()..sort()) {
|
|
sink.writeln(' $nodeClass');
|
|
}
|
|
}
|
|
}
|
|
}
|