// 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 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.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, 'identifier': node.identifier, }); 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, 'identifier': node.identifier, }); 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, 'identifier': node.identifier, '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, 'identifier': node.identifier, '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.interfaces2, }); 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 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 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.superclassConstraints2, }); 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, 'identifier': node.identifier, }); 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 visitTypeName(TypeName node) { _visitChildren(node, { 'name': node.name, 'typeArguments': node.typeArguments, 'question': node.question, }); super.visitTypeName(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.mixinTypes2, }); 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 { 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 _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 _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'); } } } /// 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; } }