// 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 main(List 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 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> 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>> 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>> lengthData = {}; /// Information about children of nodes that were not correctly recorded. Set 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 { /// 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.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, // 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 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 childMap) { var nodeClassName = _className(node); data.recordNodeClass(nodeClassName); var parent = node.parent; if (parent != null) { data.recordParentClass(nodeClassName, _className(parent)); } var visitChildren = []; 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 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 _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 _convertMap(String context, Map map) { var columns = []; 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'); } } } }