51114fca8a
Fixes https://github.com/dart-lang/language/issues/1073 Spec: https://github.com/dart-lang/language/blob/master/accepted/future-releases/unnamed-libraries/feature-specification.md This work allows library directives without a name. Every single one would look like this: ``` library; ``` :) it was a little anti-climactic implementing a non-feature like this, but there it is. The affordance for a library directive without a name is guarded by an experiment flag, `--unnamed-libraries`. Change-Id: I8612238359e88d6082f7e89d0d0fc624fdb45273 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257490 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Kevin Moore <kevmoo@google.com>
1525 lines
42 KiB
Dart
1525 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/diagnostic/diagnostic.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
|
|
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.exceptionParameter2,
|
|
'stackTraceParameter': node.stackTraceParameter2,
|
|
'body': node.body,
|
|
});
|
|
super.visitCatchClause(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassDeclaration(ClassDeclaration node) {
|
|
_visitChildren(node, {
|
|
'documentationComment': node.documentationComment,
|
|
'metadata': node.metadata,
|
|
'abstractKeyword': node.abstractKeyword,
|
|
'name': node.name,
|
|
'typeParameters': node.typeParameters,
|
|
'extendsClause': node.extendsClause,
|
|
'withClause': node.withClause,
|
|
'implementsClause': node.implementsClause,
|
|
'nativeClause': node.nativeClause,
|
|
'members': node.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.returnType,
|
|
'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, {
|
|
'components': node.components,
|
|
});
|
|
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.name,
|
|
'constants': node.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,
|
|
'extendedType': node.extendedType,
|
|
'member': node.members,
|
|
});
|
|
super.visitExtensionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionOverride(ExtensionOverride node) {
|
|
_visitChildren(node, {
|
|
'extensionName': node.extensionName,
|
|
'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.condition,
|
|
'thenElement': node.thenElement,
|
|
'elseElement': node.elseElement,
|
|
});
|
|
super.visitIfElement(node);
|
|
}
|
|
|
|
@override
|
|
void visitIfStatement(IfStatement node) {
|
|
_visitChildren(node, {
|
|
'condition': node.condition,
|
|
'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.name2,
|
|
});
|
|
super.visitLibraryDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitLibraryIdentifier(LibraryIdentifier node) {
|
|
_visitChildren(node, {
|
|
'components': node.components,
|
|
});
|
|
super.visitLibraryIdentifier(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.members,
|
|
});
|
|
super.visitMixinDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitNamedExpression(NamedExpression node) {
|
|
_visitChildren(node, {
|
|
'name': node.name,
|
|
'expression': node.expression,
|
|
});
|
|
super.visitNamedExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitNamedType(NamedType node) {
|
|
_visitChildren(node, {
|
|
'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 visitOnClause(OnClause node) {
|
|
_visitChildren(node, {
|
|
'superclassConstraints': node.superclassConstraints,
|
|
});
|
|
super.visitOnClause(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 {
|
|
final collection = AnalysisContextCollection(
|
|
includedPaths: [rootPath],
|
|
resourceProvider: PhysicalResourceProvider.INSTANCE,
|
|
);
|
|
final 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.
|
|
final 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 (hasError(resolvedUnitResult)) {
|
|
print('File $filePath skipped due to errors:');
|
|
for (var error in resolvedUnitResult.errors) {
|
|
print(' ${error.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');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return `true` if the [result] contains an error.
|
|
static bool hasError(ResolvedUnitResult result) {
|
|
for (var error in result.errors) {
|
|
if (error.severity == Severity.error) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|