Files
sdk/pkg/analysis_server/tool/code_completion/code_metrics.dart
T
Konstantin Shcheglov e9d8109258 DeCo. Support empty bodies in membered declarations
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>
2026-03-16 08:56:11 -07:00

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');
}
}
}
}