| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io' as io; | |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; | |
| import 'package:analyzer/dart/analysis/context_root.dart'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/token.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/file_system/physical_file_system.dart'; | |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; | |
| import 'package:analyzer/src/utilities/extensions/diagnostic.dart'; | |
| import 'package:args/args.dart'; | |
| /// Compute and print lexical and semantic information about a package. | |
| Future<void> main(List<String> args) async { | |
| var parser = createArgParser(); | |
| var result = parser.parse(args); | |
| if (validArguments(parser, result)) { | |
| var out = io.stdout; | |
| var rootPath = result.rest[0]; | |
| out.writeln('Analyzing root: "$rootPath"'); | |
| var computer = CodeShapeMetricsComputer(); | |
| var stopwatch = Stopwatch(); | |
| stopwatch.start(); | |
| await computer.compute(rootPath); | |
| stopwatch.stop(); | |
| var duration = Duration(milliseconds: stopwatch.elapsedMilliseconds); | |
| out.writeln(' Analysis performed in $duration'); | |
| computer.writeResults(out); | |
| await out.flush(); | |
| } | |
| io.exit(0); | |
| } | |
| /// Create a parser that can be used to parse the command-line arguments. | |
| ArgParser createArgParser() { | |
| var parser = ArgParser(); | |
| parser.addOption('help', abbr: 'h', help: 'Print this help message.'); | |
| return parser; | |
| } | |
| /// Print usage information for this tool. | |
| void printUsage(ArgParser parser, {String? error}) { | |
| if (error != null) { | |
| print(error); | |
| print(''); | |
| } | |
| print('usage: dart code_metrics.dart [options] packagePath'); | |
| print(''); | |
| print('Compute and print lexical and semantic information about a package.'); | |
| print(''); | |
| print(parser.usage); | |
| } | |
| /// Return `true` if the command-line arguments (represented by the [result] and | |
| /// parsed by the [parser]) are valid. | |
| bool validArguments(ArgParser parser, ArgResults result) { | |
| if (result.wasParsed('help')) { | |
| printUsage(parser); | |
| return false; | |
| } else if (result.rest.length != 1) { | |
| printUsage(parser, error: 'No package path specified.'); | |
| return false; | |
| } | |
| var rootPath = result.rest[0]; | |
| if (!io.Directory(rootPath).existsSync()) { | |
| printUsage(parser, error: 'The directory "$rootPath" does not exist.'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// An object that records the data used to compute the metrics. | |
| class CodeShapeData { | |
| /// A table mapping the name of a node class to the number of instances of | |
| /// that class that were visited. | |
| Map<String, int> nodeData = {}; | |
| /// A table mapping the name of a node class and the name of the node's | |
| /// parent's class to the number of instances of the node class that had a | |
| /// parent of the parent class. | |
| Map<String, Map<String, int>> parentData = {}; | |
| /// A table mapping the name of a node class, the name of a property of that | |
| /// node, and the class of the property's value to number of instances of the | |
| /// node class that had a value of the property class for the property. | |
| Map<String, Map<String, Map<String, int>>> childData = {}; | |
| /// A table mapping the name of a node class, the name of a list-valued | |
| /// property of that node, and the length of the list to number of instances | |
| /// of the node class that had a value of that length for the property. | |
| Map<String, Map<String, Map<int, int>>> lengthData = {}; | |
| /// Information about children of nodes that were not correctly recorded. | |
| Set<String> missedChildren = {}; | |
| /// Initialize a newly created set of relevance data to be empty. | |
| new(); | |
| /// Record that an element of the given [node] was found in the given | |
| /// [context]. | |
| void recordNode(String nodeClassName, String property, AstNode node) { | |
| var childClass = node.runtimeType.toString(); | |
| if (childClass.endsWith('Impl')) { | |
| childClass = childClass.substring(0, childClass.length - 4); | |
| } | |
| _recordChildData(nodeClassName, property, childClass); | |
| } | |
| /// Record that a node that is an instance of the [nodeClassName] was visited. | |
| void recordNodeClass(String nodeClassName) { | |
| nodeData[nodeClassName] = (nodeData[nodeClassName] ?? 0) + 1; | |
| } | |
| void recordNodeList(String nodeClassName, String property, NodeList list) { | |
| _recordListLength(nodeClassName, property, list); | |
| for (var element in list) { | |
| recordNode(nodeClassName, property, element); | |
| } | |
| } | |
| void recordParentClass(String nodeClassName, String parentClassName) { | |
| var classMap = parentData.putIfAbsent(nodeClassName, () => {}); | |
| classMap[parentClassName] = (classMap[parentClassName] ?? 0) + 1; | |
| } | |
| void recordToken(String nodeClassName, String property, Token? token) { | |
| var lexeme = token?.lexeme ?? 'null'; | |
| _recordChildData(nodeClassName, property, lexeme); | |
| } | |
| void _recordChildData( | |
| String nodeClassName, | |
| String property, | |
| String childValue, | |
| ) { | |
| var classMap = childData.putIfAbsent(nodeClassName, () => {}); | |
| var propertyMap = classMap.putIfAbsent(property, () => {}); | |
| propertyMap[childValue] = (propertyMap[childValue] ?? 0) + 1; | |
| } | |
| /// Record that an element of the given [node] was found in the given | |
| /// [context]. | |
| void _recordListLength(String nodeClassName, String property, NodeList list) { | |
| var classMap = lengthData.putIfAbsent(nodeClassName, () => {}); | |
| var propertyMap = classMap.putIfAbsent(property, () => {}); | |
| var length = list.length; | |
| propertyMap[length] = (propertyMap[length] ?? 0) + 1; | |
| } | |
| } | |
| /// An object that visits a compilation unit in order to record the data used to | |
| /// compute the metrics. | |
| class CodeShapeDataCollector extends RecursiveAstVisitor<void> { | |
| /// The data being collected. | |
| final CodeShapeData data; | |
| /// Initialize a newly created collector to add data points to the given | |
| /// [data]. | |
| new(this.data); | |
| @override | |
| void visitAdjacentStrings(AdjacentStrings node) { | |
| _visitChildren(node, {'strings': node.strings}); | |
| super.visitAdjacentStrings(node); | |
| } | |
| @override | |
| void visitAnnotation(Annotation node) { | |
| _visitChildren(node, { | |
| 'name': node.name, | |
| 'constructorName': node.constructorName, | |
| 'arguments': node.arguments, | |
| }); | |
| super.visitAnnotation(node); | |
| } | |
| @override | |
| void visitArgumentList(ArgumentList node) { | |
| _visitChildren(node, {'arguments': node.arguments}); | |
| super.visitArgumentList(node); | |
| } | |
| @override | |
| void visitAsExpression(AsExpression node) { | |
| _visitChildren(node, {'expression': node.expression, 'type': node.type}); | |
| super.visitAsExpression(node); | |
| } | |
| @override | |
| void visitAssertInitializer(AssertInitializer node) { | |
| _visitChildren(node, { | |
| 'condition': node.condition, | |
| 'message': node.message, | |
| }); | |
| super.visitAssertInitializer(node); | |
| } | |
| @override | |
| void visitAssertStatement(AssertStatement node) { | |
| _visitChildren(node, { | |
| 'condition': node.condition, | |
| 'message': node.message, | |
| }); | |
| super.visitAssertStatement(node); | |
| } | |
| @override | |
| void visitAssignmentExpression(AssignmentExpression node) { | |
| _visitChildren(node, { | |
| 'leftHandSide': node.leftHandSide, | |
| 'operator': node.operator, | |
| 'rightHandSide': node.rightHandSide, | |
| }); | |
| super.visitAssignmentExpression(node); | |
| } | |
| @override | |
| void visitAwaitExpression(AwaitExpression node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitAwaitExpression(node); | |
| } | |
| @override | |
| void visitBinaryExpression(BinaryExpression node) { | |
| _visitChildren(node, { | |
| 'leftOperand': node.leftOperand, | |
| 'operator': node.operator, | |
| 'rightOperand': node.rightOperand, | |
| }); | |
| super.visitBinaryExpression(node); | |
| } | |
| @override | |
| void visitBlock(Block node) { | |
| _visitChildren(node, {'statement': node.statements}); | |
| super.visitBlock(node); | |
| } | |
| @override | |
| void visitBlockFunctionBody(BlockFunctionBody node) { | |
| _visitChildren(node, { | |
| 'keyword': node.keyword, | |
| 'star': node.star, | |
| 'block': node.block, | |
| }); | |
| super.visitBlockFunctionBody(node); | |
| } | |
| @override | |
| void visitBooleanLiteral(BooleanLiteral node) { | |
| _visitChildren(node, {'literal': node.literal}); | |
| super.visitBooleanLiteral(node); | |
| } | |
| @override | |
| void visitBreakStatement(BreakStatement node) { | |
| _visitChildren(node, {'label': node.label}); | |
| super.visitBreakStatement(node); | |
| } | |
| @override | |
| void visitCascadeExpression(CascadeExpression node) { | |
| _visitChildren(node, { | |
| 'target': node.target, | |
| 'cascadeSections': node.cascadeSections, | |
| }); | |
| super.visitCascadeExpression(node); | |
| } | |
| @override | |
| void visitCatchClause(CatchClause node) { | |
| _visitChildren(node, { | |
| 'exceptionType': node.exceptionType, | |
| 'exceptionParameter': node.exceptionParameter, | |
| 'stackTraceParameter': node.stackTraceParameter, | |
| 'body': node.body, | |
| }); | |
| super.visitCatchClause(node); | |
| } | |
| @override | |
| void visitClassDeclaration(ClassDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'abstractKeyword': node.abstractKeyword, | |
| 'name': node.namePart.typeName, | |
| 'typeParameters': node.namePart.typeParameters, | |
| 'extendsClause': node.extendsClause, | |
| 'withClause': node.withClause, | |
| 'implementsClause': node.implementsClause, | |
| 'nativeClause': node.nativeClause, | |
| 'members': node.body.members, | |
| }); | |
| super.visitClassDeclaration(node); | |
| } | |
| @override | |
| void visitClassTypeAlias(ClassTypeAlias node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'abstractKeyword': node.abstractKeyword, | |
| 'name': node.name, | |
| 'typeParameters': node.typeParameters, | |
| 'superclass': node.superclass, | |
| 'withClause': node.withClause, | |
| 'implementsClause': node.implementsClause, | |
| }); | |
| super.visitClassTypeAlias(node); | |
| } | |
| @override | |
| void visitComment(Comment node) { | |
| _visitChildren(node, {'references': node.references}); | |
| super.visitComment(node); | |
| } | |
| @override | |
| void visitCommentReference(CommentReference node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitCommentReference(node); | |
| } | |
| @override | |
| void visitCompilationUnit(CompilationUnit node) { | |
| _visitChildren(node, { | |
| 'scriptTag': node.scriptTag, | |
| 'directives': node.directives, | |
| 'declarations': node.declarations, | |
| }); | |
| super.visitCompilationUnit(node); | |
| } | |
| @override | |
| void visitConditionalExpression(ConditionalExpression node) { | |
| _visitChildren(node, { | |
| 'condition': node.condition, | |
| 'thenExpression': node.thenExpression, | |
| 'elseExpression': node.elseExpression, | |
| }); | |
| super.visitConditionalExpression(node); | |
| } | |
| @override | |
| void visitConfiguration(Configuration node) { | |
| _visitChildren(node, { | |
| 'name': node.name, | |
| 'value': node.value, | |
| 'uri': node.uri, | |
| }); | |
| super.visitConfiguration(node); | |
| } | |
| @override | |
| void visitConstructorDeclaration(ConstructorDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'externalKeyword': node.externalKeyword, | |
| 'constKeyword': node.constKeyword, | |
| 'factoryKeyword': node.factoryKeyword, | |
| 'returnType': ?node.typeName, | |
| 'name': node.name, | |
| 'parameters': node.parameters, | |
| 'initializers': node.initializers, | |
| 'redirectedConstructor': node.redirectedConstructor, | |
| 'body': node.body, | |
| }); | |
| super.visitConstructorDeclaration(node); | |
| } | |
| @override | |
| void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | |
| _visitChildren(node, { | |
| 'thisKeyword': node.thisKeyword, | |
| 'fieldName': node.fieldName, | |
| 'expression': node.expression, | |
| }); | |
| super.visitConstructorFieldInitializer(node); | |
| } | |
| @override | |
| void visitConstructorName(ConstructorName node) { | |
| _visitChildren(node, {'type': node.type, 'name': node.name}); | |
| super.visitConstructorName(node); | |
| } | |
| @override | |
| void visitContinueStatement(ContinueStatement node) { | |
| _visitChildren(node, {'label': node.label}); | |
| super.visitContinueStatement(node); | |
| } | |
| @override | |
| void visitDeclaredIdentifier(DeclaredIdentifier node) { | |
| _visitChildren(node, { | |
| 'keyword': node.keyword, | |
| 'type': node.type, | |
| 'name': node.name, | |
| }); | |
| super.visitDeclaredIdentifier(node); | |
| } | |
| @override | |
| void visitDoStatement(DoStatement node) { | |
| _visitChildren(node, {'body': node.body, 'condition': node.condition}); | |
| super.visitDoStatement(node); | |
| } | |
| @override | |
| void visitDottedName(DottedName node) { | |
| _visitChildren(node, {}); | |
| super.visitDottedName(node); | |
| } | |
| @override | |
| void visitDoubleLiteral(DoubleLiteral node) { | |
| _visitChildren(node, {}); | |
| super.visitDoubleLiteral(node); | |
| } | |
| @override | |
| void visitEmptyFunctionBody(EmptyFunctionBody node) { | |
| _visitChildren(node, {}); | |
| super.visitEmptyFunctionBody(node); | |
| } | |
| @override | |
| void visitEmptyStatement(EmptyStatement node) { | |
| _visitChildren(node, {}); | |
| super.visitEmptyStatement(node); | |
| } | |
| @override | |
| void visitEnumConstantDeclaration(EnumConstantDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.name, | |
| }); | |
| super.visitEnumConstantDeclaration(node); | |
| } | |
| @override | |
| void visitEnumDeclaration(EnumDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.namePart.typeName, | |
| 'constants': node.body.constants, | |
| }); | |
| super.visitEnumDeclaration(node); | |
| } | |
| @override | |
| void visitExportDirective(ExportDirective node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'uri': node.uri, | |
| 'configurations': node.configurations, | |
| 'combinators': node.combinators, | |
| }); | |
| super.visitExportDirective(node); | |
| } | |
| @override | |
| void visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| _visitChildren(node, { | |
| 'keyword': node.keyword, | |
| 'star': node.star, | |
| 'expression': node.expression, | |
| }); | |
| super.visitExpressionFunctionBody(node); | |
| } | |
| @override | |
| void visitExpressionStatement(ExpressionStatement node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitExpressionStatement(node); | |
| } | |
| @override | |
| void visitExtendsClause(ExtendsClause node) { | |
| _visitChildren(node, {'superclass': node.superclass}); | |
| super.visitExtendsClause(node); | |
| } | |
| @override | |
| void visitExtensionDeclaration(ExtensionDeclaration node) { | |
| _visitChildren(node, { | |
| 'name': node.name, | |
| 'typeParameters': node.typeParameters, | |
| 'onClause': node.onClause, | |
| 'member': node.body.members, | |
| }); | |
| super.visitExtensionDeclaration(node); | |
| } | |
| @override | |
| void visitExtensionOverride(ExtensionOverride node) { | |
| _visitChildren(node, { | |
| 'importPrefix': node.importPrefix, | |
| 'name': node.name, | |
| 'typeArguments': node.typeArguments, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitExtensionOverride(node); | |
| } | |
| @override | |
| void visitFieldDeclaration(FieldDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'abstractKeyword': node.abstractKeyword, | |
| 'covariantKeyword': node.covariantKeyword, | |
| 'externalKeyword': node.externalKeyword, | |
| 'staticKeyword': node.staticKeyword, | |
| 'fields': node.fields, | |
| }); | |
| super.visitFieldDeclaration(node); | |
| } | |
| @override | |
| void visitFieldFormalParameter(FieldFormalParameter node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'covariantKeyword': node.covariantKeyword, | |
| 'requiredKeyword': node.requiredKeyword, | |
| 'constFinalOrVarKeyword': node.constFinalOrVarKeyword, | |
| 'type': node.type, | |
| 'thisKeyword': node.thisKeyword, | |
| 'period': node.period, | |
| 'name': node.name, | |
| 'functionTypedSuffix': node.functionTypedSuffix, | |
| 'defaultClause': node.defaultClause, | |
| }); | |
| super.visitFieldFormalParameter(node); | |
| } | |
| @override | |
| void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) { | |
| _visitChildren(node, { | |
| 'loopVariable': node.loopVariable, | |
| 'iterable': node.iterable, | |
| }); | |
| super.visitForEachPartsWithDeclaration(node); | |
| } | |
| @override | |
| void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) { | |
| _visitChildren(node, { | |
| 'identifier': node.identifier, | |
| 'iterable': node.iterable, | |
| }); | |
| super.visitForEachPartsWithIdentifier(node); | |
| } | |
| @override | |
| void visitForElement(ForElement node) { | |
| _visitChildren(node, { | |
| 'awaitKeyword': node.awaitKeyword, | |
| 'forLoopParts': node.forLoopParts, | |
| 'body': node.body, | |
| }); | |
| super.visitForElement(node); | |
| } | |
| @override | |
| void visitFormalParameterDefaultClause(FormalParameterDefaultClause node) { | |
| _visitChildren(node, {'separator': node.separator, 'value': node.value}); | |
| super.visitFormalParameterDefaultClause(node); | |
| } | |
| @override | |
| void visitFormalParameterList(FormalParameterList node) { | |
| _visitChildren(node, {'parameters': node.parameters}); | |
| super.visitFormalParameterList(node); | |
| } | |
| @override | |
| void visitForPartsWithDeclarations(ForPartsWithDeclarations node) { | |
| _visitChildren(node, { | |
| 'variables': node.variables, | |
| 'condition': node.condition, | |
| 'updater': node.updaters, | |
| }); | |
| super.visitForPartsWithDeclarations(node); | |
| } | |
| @override | |
| void visitForPartsWithExpression(ForPartsWithExpression node) { | |
| _visitChildren(node, { | |
| 'initialization': node.initialization, | |
| 'condition': node.condition, | |
| 'updater': node.updaters, | |
| }); | |
| super.visitForPartsWithExpression(node); | |
| } | |
| @override | |
| void visitForStatement(ForStatement node) { | |
| _visitChildren(node, { | |
| 'awaitKeyword': node.awaitKeyword, | |
| 'forLoopParts': node.forLoopParts, | |
| 'body': node.body, | |
| }); | |
| super.visitForStatement(node); | |
| } | |
| @override | |
| void visitFunctionDeclaration(FunctionDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'externalKeyword': node.externalKeyword, | |
| 'propertyKeyword': node.propertyKeyword, | |
| 'name': node.name, | |
| 'functionExpression': node.functionExpression, | |
| 'returnType': node.returnType, | |
| }); | |
| super.visitFunctionDeclaration(node); | |
| } | |
| @override | |
| void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { | |
| _visitChildren(node, {'functionDeclaration': node.functionDeclaration}); | |
| super.visitFunctionDeclarationStatement(node); | |
| } | |
| @override | |
| void visitFunctionExpression(FunctionExpression node) { | |
| _visitChildren(node, { | |
| 'typeParameters': node.typeParameters, | |
| 'parameters': node.parameters, | |
| 'body': node.body, | |
| }); | |
| super.visitFunctionExpression(node); | |
| } | |
| @override | |
| void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | |
| _visitChildren(node, { | |
| 'function': node.function, | |
| 'typeArguments': node.typeArguments, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitFunctionExpressionInvocation(node); | |
| } | |
| @override | |
| void visitFunctionTypeAlias(FunctionTypeAlias node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'returnType': node.returnType, | |
| 'name': node.name, | |
| 'typeParameters': node.typeParameters, | |
| 'parameters': node.parameters, | |
| }); | |
| super.visitFunctionTypeAlias(node); | |
| } | |
| @override | |
| void visitFunctionTypedFormalParameterSuffix( | |
| FunctionTypedFormalParameterSuffix node, | |
| ) { | |
| _visitChildren(node, { | |
| 'typeParameters': node.typeParameters, | |
| 'formalParameters': node.formalParameters, | |
| 'question': node.question, | |
| }); | |
| super.visitFunctionTypedFormalParameterSuffix(node); | |
| } | |
| @override | |
| void visitGenericFunctionType(GenericFunctionType node) { | |
| _visitChildren(node, { | |
| 'returnType': node.returnType, | |
| 'typeParameters': node.typeParameters, | |
| 'parameters': node.parameters, | |
| 'question': node.question, | |
| }); | |
| super.visitGenericFunctionType(node); | |
| } | |
| @override | |
| void visitGenericTypeAlias(GenericTypeAlias node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.name, | |
| 'typeParameters': node.typeParameters, | |
| 'functionType': node.functionType, | |
| }); | |
| super.visitGenericTypeAlias(node); | |
| } | |
| @override | |
| void visitHideCombinator(HideCombinator node) { | |
| _visitChildren(node, {'hiddenNames': node.hiddenNames}); | |
| super.visitHideCombinator(node); | |
| } | |
| @override | |
| void visitIfElement(IfElement node) { | |
| _visitChildren(node, { | |
| 'condition': node.expression, | |
| 'thenElement': node.thenElement, | |
| 'elseElement': node.elseElement, | |
| }); | |
| super.visitIfElement(node); | |
| } | |
| @override | |
| void visitIfStatement(IfStatement node) { | |
| _visitChildren(node, { | |
| 'condition': node.expression, | |
| 'thenStatement': node.thenStatement, | |
| 'elseStatement': node.elseStatement, | |
| }); | |
| super.visitIfStatement(node); | |
| } | |
| @override | |
| void visitImplementsClause(ImplementsClause node) { | |
| _visitChildren(node, {'interfaces': node.interfaces}); | |
| super.visitImplementsClause(node); | |
| } | |
| @override | |
| void visitImportDirective(ImportDirective node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'uri': node.uri, | |
| 'deferredKeyword': node.deferredKeyword, | |
| 'asKeyword': node.asKeyword, | |
| 'prefix': node.prefix, | |
| 'configurations': node.configurations, | |
| 'combinators': node.combinators, | |
| }); | |
| super.visitImportDirective(node); | |
| } | |
| @override | |
| void visitIndexExpression(IndexExpression node) { | |
| _visitChildren(node, {'target': node.target, 'index': node.index}); | |
| super.visitIndexExpression(node); | |
| } | |
| @override | |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| _visitChildren(node, { | |
| 'keyword': node.keyword, | |
| 'constructorName': node.constructorName, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitInstanceCreationExpression(node); | |
| } | |
| @override | |
| void visitIntegerLiteral(IntegerLiteral node) { | |
| var value = node.value; | |
| if (value == -1 || value == 0 || value == 1) { | |
| _visitChildren(node, {'literal': node.literal}); | |
| } else { | |
| _visitChildren(node, {}); | |
| } | |
| super.visitIntegerLiteral(node); | |
| } | |
| @override | |
| void visitInterpolationExpression(InterpolationExpression node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitInterpolationExpression(node); | |
| } | |
| @override | |
| void visitInterpolationString(InterpolationString node) { | |
| _visitChildren(node, {}); | |
| super.visitInterpolationString(node); | |
| } | |
| @override | |
| void visitIsExpression(IsExpression node) { | |
| _visitChildren(node, {'expression': node.expression, 'type': node.type}); | |
| super.visitIsExpression(node); | |
| } | |
| @override | |
| void visitLabel(Label node) { | |
| _visitChildren(node, {'label': node.name}); | |
| super.visitLabel(node); | |
| } | |
| @override | |
| void visitLabeledStatement(LabeledStatement node) { | |
| _visitChildren(node, {'statement': node.statement}); | |
| super.visitLabeledStatement(node); | |
| } | |
| @override | |
| void visitLibraryDirective(LibraryDirective node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.name, | |
| }); | |
| super.visitLibraryDirective(node); | |
| } | |
| @override | |
| void visitListLiteral(ListLiteral node) { | |
| _visitChildren(node, { | |
| 'typeArguments': node.typeArguments, | |
| 'elements': node.elements, | |
| }); | |
| super.visitListLiteral(node); | |
| } | |
| @override | |
| void visitMapLiteralEntry(MapLiteralEntry node) { | |
| _visitChildren(node, {'key': node.key, 'value': node.value}); | |
| super.visitMapLiteralEntry(node); | |
| } | |
| @override | |
| void visitMethodDeclaration(MethodDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'externalKeyword': node.externalKeyword, | |
| 'modifierKeyword': node.modifierKeyword, | |
| 'returnType': node.returnType, | |
| 'name': node.name, | |
| 'operatorKeyword': node.operatorKeyword, | |
| 'typeParameters': node.typeParameters, | |
| 'parameters': node.parameters, | |
| 'body': node.body, | |
| }); | |
| super.visitMethodDeclaration(node); | |
| } | |
| @override | |
| void visitMethodInvocation(MethodInvocation node) { | |
| _visitChildren(node, { | |
| 'target': node.target, | |
| 'methodName': node.methodName, | |
| 'typeArguments': node.typeArguments, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitMethodInvocation(node); | |
| } | |
| @override | |
| void visitMixinDeclaration(MixinDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.name, | |
| 'typeParameters': node.typeParameters, | |
| 'onClause': node.onClause, | |
| 'implementsClause': node.implementsClause, | |
| 'members': node.body.members, | |
| }); | |
| super.visitMixinDeclaration(node); | |
| } | |
| @override | |
| void visitMixinOnClause(MixinOnClause node) { | |
| _visitChildren(node, {'superclassConstraints': node.superclassConstraints}); | |
| super.visitMixinOnClause(node); | |
| } | |
| @override | |
| void visitNamedArgument(NamedArgument node) { | |
| _visitChildren(node, {'argumentExpression': node.argumentExpression}); | |
| super.visitNamedArgument(node); | |
| } | |
| @override | |
| void visitNamedType(NamedType node) { | |
| _visitChildren(node, { | |
| 'importPrefix': node.importPrefix, | |
| 'name': node.name, | |
| 'typeArguments': node.typeArguments, | |
| 'question': node.question, | |
| }); | |
| super.visitNamedType(node); | |
| } | |
| @override | |
| void visitNativeClause(NativeClause node) { | |
| _visitChildren(node, {'name': node.name}); | |
| super.visitNativeClause(node); | |
| } | |
| @override | |
| void visitNativeFunctionBody(NativeFunctionBody node) { | |
| _visitChildren(node, {}); | |
| super.visitNativeFunctionBody(node); | |
| } | |
| @override | |
| void visitNullLiteral(NullLiteral node) { | |
| _visitChildren(node, {}); | |
| super.visitNullLiteral(node); | |
| } | |
| @override | |
| void visitParenthesizedExpression(ParenthesizedExpression node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitParenthesizedExpression(node); | |
| } | |
| @override | |
| void visitPartDirective(PartDirective node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'uri': node.uri, | |
| }); | |
| super.visitPartDirective(node); | |
| } | |
| @override | |
| void visitPartOfDirective(PartOfDirective node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'libraryName': node.libraryName, | |
| 'uri': node.uri, | |
| }); | |
| super.visitPartOfDirective(node); | |
| } | |
| @override | |
| void visitPostfixExpression(PostfixExpression node) { | |
| _visitChildren(node, {'operand': node.operand, 'operator': node.operator}); | |
| super.visitPostfixExpression(node); | |
| } | |
| @override | |
| void visitPrefixedIdentifier(PrefixedIdentifier node) { | |
| _visitChildren(node, { | |
| 'prefix': node.prefix, | |
| 'identifier': node.identifier, | |
| }); | |
| super.visitPrefixedIdentifier(node); | |
| } | |
| @override | |
| void visitPrefixExpression(PrefixExpression node) { | |
| _visitChildren(node, {'operator': node.operator, 'operand': node.operand}); | |
| super.visitPrefixExpression(node); | |
| } | |
| @override | |
| void visitPropertyAccess(PropertyAccess node) { | |
| _visitChildren(node, { | |
| 'target': node.target, | |
| 'propertyName': node.propertyName, | |
| }); | |
| super.visitPropertyAccess(node); | |
| } | |
| @override | |
| void visitRedirectingConstructorInvocation( | |
| RedirectingConstructorInvocation node, | |
| ) { | |
| _visitChildren(node, { | |
| 'constructorName': node.constructorName, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitRedirectingConstructorInvocation(node); | |
| } | |
| @override | |
| void visitRegularFormalParameter(RegularFormalParameter node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'covariantKeyword': node.covariantKeyword, | |
| 'requiredKeyword': node.requiredKeyword, | |
| 'constFinalOrVarKeyword': node.constFinalOrVarKeyword, | |
| 'type': node.type, | |
| 'name': node.name, | |
| 'functionTypedSuffix': node.functionTypedSuffix, | |
| 'defaultClause': node.defaultClause, | |
| }); | |
| super.visitRegularFormalParameter(node); | |
| } | |
| @override | |
| void visitRethrowExpression(RethrowExpression node) { | |
| _visitChildren(node, {}); | |
| super.visitRethrowExpression(node); | |
| } | |
| @override | |
| void visitReturnStatement(ReturnStatement node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitReturnStatement(node); | |
| } | |
| @override | |
| void visitScriptTag(ScriptTag node) { | |
| _visitChildren(node, {}); | |
| super.visitScriptTag(node); | |
| } | |
| @override | |
| void visitSetOrMapLiteral(SetOrMapLiteral node) { | |
| _visitChildren(node, { | |
| 'typeArguments': node.typeArguments, | |
| 'elements': node.elements, | |
| }); | |
| super.visitSetOrMapLiteral(node); | |
| } | |
| @override | |
| void visitShowCombinator(ShowCombinator node) { | |
| _visitChildren(node, {'shownNames': node.shownNames}); | |
| super.visitShowCombinator(node); | |
| } | |
| @override | |
| void visitSimpleStringLiteral(SimpleStringLiteral node) { | |
| _visitChildren(node, {}); | |
| super.visitSimpleStringLiteral(node); | |
| } | |
| @override | |
| void visitSpreadElement(SpreadElement node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitSpreadElement(node); | |
| } | |
| @override | |
| void visitStringInterpolation(StringInterpolation node) { | |
| _visitChildren(node, {'elements': node.elements}); | |
| super.visitStringInterpolation(node); | |
| } | |
| @override | |
| void visitSuperConstructorInvocation(SuperConstructorInvocation node) { | |
| _visitChildren(node, { | |
| 'constructorName': node.constructorName, | |
| 'argumentList': node.argumentList, | |
| }); | |
| super.visitSuperConstructorInvocation(node); | |
| } | |
| @override | |
| void visitSuperExpression(SuperExpression node) { | |
| _visitChildren(node, {}); | |
| super.visitSuperExpression(node); | |
| } | |
| @override | |
| void visitSuperFormalParameter(SuperFormalParameter node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'covariantKeyword': node.covariantKeyword, | |
| 'requiredKeyword': node.requiredKeyword, | |
| 'constFinalOrVarKeyword': node.constFinalOrVarKeyword, | |
| 'type': node.type, | |
| 'superKeyword': node.superKeyword, | |
| 'period': node.period, | |
| 'name': node.name, | |
| 'functionTypedSuffix': node.functionTypedSuffix, | |
| 'defaultClause': node.defaultClause, | |
| }); | |
| super.visitSuperFormalParameter(node); | |
| } | |
| @override | |
| void visitSwitchCase(SwitchCase node) { | |
| _visitChildren(node, { | |
| 'labels': node.labels, | |
| 'expression': node.expression, | |
| 'statements': node.statements, | |
| }); | |
| super.visitSwitchCase(node); | |
| } | |
| @override | |
| void visitSwitchDefault(SwitchDefault node) { | |
| _visitChildren(node, {'statements': node.statements}); | |
| super.visitSwitchDefault(node); | |
| } | |
| @override | |
| void visitSwitchStatement(SwitchStatement node) { | |
| _visitChildren(node, { | |
| 'expression': node.expression, | |
| 'members': node.members, | |
| }); | |
| super.visitSwitchStatement(node); | |
| } | |
| @override | |
| void visitSymbolLiteral(SymbolLiteral node) { | |
| _visitChildren(node, {}); | |
| super.visitSymbolLiteral(node); | |
| } | |
| @override | |
| void visitThisExpression(ThisExpression node) { | |
| _visitChildren(node, {}); | |
| super.visitThisExpression(node); | |
| } | |
| @override | |
| void visitThrowExpression(ThrowExpression node) { | |
| _visitChildren(node, {'expression': node.expression}); | |
| super.visitThrowExpression(node); | |
| } | |
| @override | |
| void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'externalKeyword': node.externalKeyword, | |
| 'variables': node.variables, | |
| }); | |
| super.visitTopLevelVariableDeclaration(node); | |
| } | |
| @override | |
| void visitTryStatement(TryStatement node) { | |
| _visitChildren(node, { | |
| 'body': node.body, | |
| 'catchClauses': node.catchClauses, | |
| 'finallyBlock': node.finallyBlock, | |
| }); | |
| super.visitTryStatement(node); | |
| } | |
| @override | |
| void visitTypeArgumentList(TypeArgumentList node) { | |
| _visitChildren(node, {'arguments': node.arguments}); | |
| super.visitTypeArgumentList(node); | |
| } | |
| @override | |
| void visitTypeParameter(TypeParameter node) { | |
| _visitChildren(node, {'name': node.name, 'bound': node.bound}); | |
| super.visitTypeParameter(node); | |
| } | |
| @override | |
| void visitTypeParameterList(TypeParameterList node) { | |
| _visitChildren(node, {'typeParameters': node.typeParameters}); | |
| super.visitTypeParameterList(node); | |
| } | |
| @override | |
| void visitVariableDeclaration(VariableDeclaration node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'name': node.name, | |
| 'initializer': node.initializer, | |
| }); | |
| super.visitVariableDeclaration(node); | |
| } | |
| @override | |
| void visitVariableDeclarationList(VariableDeclarationList node) { | |
| _visitChildren(node, { | |
| 'documentationComment': node.documentationComment, | |
| 'metadata': node.metadata, | |
| 'type': node.type, | |
| 'variables': node.variables, | |
| }); | |
| super.visitVariableDeclarationList(node); | |
| } | |
| @override | |
| void visitVariableDeclarationStatement(VariableDeclarationStatement node) { | |
| _visitChildren(node, {'variables': node.variables}); | |
| super.visitVariableDeclarationStatement(node); | |
| } | |
| @override | |
| void visitWhileStatement(WhileStatement node) { | |
| _visitChildren(node, {'condition': node.condition, 'body': node.body}); | |
| super.visitWhileStatement(node); | |
| } | |
| @override | |
| void visitWithClause(WithClause node) { | |
| _visitChildren(node, {'mixinTypes': node.mixinTypes}); | |
| super.visitWithClause(node); | |
| } | |
| @override | |
| void visitYieldStatement(YieldStatement node) { | |
| _visitChildren(node, {'star': node.star, 'expression': node.expression}); | |
| super.visitYieldStatement(node); | |
| } | |
| String _className(AstNode node) { | |
| var className = node.runtimeType.toString(); | |
| if (className.endsWith('Impl')) { | |
| className = className.substring(0, className.length - 4); | |
| } | |
| return className; | |
| } | |
| /// Visit the children of a node. The node is an instance of the class named | |
| /// [parentClass] and the children are in the [childMap], keyed by the name of | |
| /// the child property. | |
| void _visitChildren(AstNode node, Map<String, Object?> childMap) { | |
| var nodeClassName = _className(node); | |
| data.recordNodeClass(nodeClassName); | |
| var parent = node.parent; | |
| if (parent != null) { | |
| data.recordParentClass(nodeClassName, _className(parent)); | |
| } | |
| var visitChildren = <AstNode>[]; | |
| for (var entry in childMap.entries) { | |
| var property = entry.key; | |
| var child = entry.value; | |
| if (child is AstNode) { | |
| data.recordNode(nodeClassName, property, child); | |
| visitChildren.add(child); | |
| } else if (child is NodeList) { | |
| data.recordNodeList(nodeClassName, property, child); | |
| visitChildren.addAll(child); | |
| } else if (child is Token?) { | |
| data.recordToken(nodeClassName, property, child); | |
| } else { | |
| throw ArgumentError('Unknown class of child: ${child.runtimeType}'); | |
| } | |
| } | |
| // Validate that all AST node children were included in the [childMap]. | |
| for (var entity in node.childEntities) { | |
| if (entity is AstNode && !visitChildren.contains(entity)) { | |
| data.missedChildren.add('$nodeClassName (${entity.runtimeType})'); | |
| } | |
| } | |
| } | |
| } | |
| /// An object used to compute metrics for a single file or directory. | |
| class CodeShapeMetricsComputer { | |
| /// The metrics data that was computed. | |
| final CodeShapeData data = CodeShapeData(); | |
| /// Initialize a newly created metrics computer that can compute the metrics | |
| /// in one or more files and directories. | |
| new(); | |
| /// Compute the metrics for the file(s) in the [rootPath]. | |
| Future<void> compute(String rootPath) async { | |
| var collection = AnalysisContextCollection( | |
| includedPaths: [rootPath], | |
| resourceProvider: PhysicalResourceProvider.INSTANCE, | |
| ); | |
| var collector = CodeShapeDataCollector(data); | |
| for (var context in collection.contexts) { | |
| await _computeInContext(context.contextRoot, collector); | |
| } | |
| } | |
| /// Write a report of the metrics that were computed to the [sink]. | |
| void writeResults(StringSink sink) { | |
| // Write error information. | |
| _writeMissedChildren(sink); | |
| // Write normal information. | |
| _writeChildData(sink); | |
| } | |
| /// Compute the metrics for the files in the context [root], creating a | |
| /// separate context collection to prevent accumulating memory. The metrics | |
| /// should be captured in the [collector]. Include additional details in the | |
| /// output if [verbose] is `true`. | |
| Future<void> _computeInContext( | |
| ContextRoot root, | |
| CodeShapeDataCollector collector, | |
| ) async { | |
| // Create a new collection to avoid consuming large quantities of memory. | |
| var collection = AnalysisContextCollection( | |
| includedPaths: root.includedPaths.toList(), | |
| 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'); | |
| } | |
| } | |
| } | |
| } |