afcfbbeba8
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the packages owned by the developer experience team to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the packages' SDK constraints to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Since this change requires bumping SDK constaints to `3.13.0-0`, it was only performed on packages that are *not* published on pub. (Packages that *are* published on pub should remain on lower language versions until at least after the stable version of 3.13 is released, so that we don't block users on the stable channel from receiving updates to those packages.) Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
1435 lines
42 KiB
Dart
1435 lines
42 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: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: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.
|
|
new();
|
|
|
|
/// 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].
|
|
new(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.body.members,
|
|
});
|
|
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,
|
|
'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 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.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.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,
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'requiredKeyword': node.requiredKeyword,
|
|
'constFinalOrVarKeyword': node.constFinalOrVarKeyword,
|
|
'type': node.type,
|
|
'thisKeyword': node.thisKeyword,
|
|
'period': node.period,
|
|
'name': node.name,
|
|
'functionTypedSuffix': node.functionTypedSuffix,
|
|
'defaultClause': node.defaultClause,
|
|
});
|
|
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 visitFormalParameterDefaultClause(FormalParameterDefaultClause node) {
|
|
_visitChildren(node, {'separator': node.separator, 'value': node.value});
|
|
super.visitFormalParameterDefaultClause(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 visitFunctionTypedFormalParameterSuffix(
|
|
FunctionTypedFormalParameterSuffix node,
|
|
) {
|
|
_visitChildren(node, {
|
|
'typeParameters': node.typeParameters,
|
|
'formalParameters': node.formalParameters,
|
|
'question': node.question,
|
|
});
|
|
super.visitFunctionTypedFormalParameterSuffix(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.name});
|
|
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.members,
|
|
});
|
|
super.visitMixinDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinOnClause(MixinOnClause node) {
|
|
_visitChildren(node, {'superclassConstraints': node.superclassConstraints});
|
|
super.visitMixinOnClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitNamedArgument(NamedArgument node) {
|
|
_visitChildren(node, {'argumentExpression': node.argumentExpression});
|
|
super.visitNamedArgument(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 visitRegularFormalParameter(RegularFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'requiredKeyword': node.requiredKeyword,
|
|
'constFinalOrVarKeyword': node.constFinalOrVarKeyword,
|
|
'type': node.type,
|
|
'name': node.name,
|
|
'functionTypedSuffix': node.functionTypedSuffix,
|
|
'defaultClause': node.defaultClause,
|
|
});
|
|
super.visitRegularFormalParameter(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 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 visitSuperFormalParameter(SuperFormalParameter node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'covariantKeyword': node.covariantKeyword,
|
|
'requiredKeyword': node.requiredKeyword,
|
|
'constFinalOrVarKeyword': node.constFinalOrVarKeyword,
|
|
'type': node.type,
|
|
'superKeyword': node.superKeyword,
|
|
'period': node.period,
|
|
'name': node.name,
|
|
'functionTypedSuffix': node.functionTypedSuffix,
|
|
'defaultClause': node.defaultClause,
|
|
});
|
|
super.visitSuperFormalParameter(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.
|
|
new();
|
|
|
|
/// 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');
|
|
}
|
|
}
|
|
}
|
|
}
|