blob: 6541d6d64caf943db7da1850869d2455579ff20d [file] [log] [blame]
// 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:math' as math;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:scrape/scrape.dart';
/// The paths to each build() method and its maximum nesting level.
final buildMethods = <String, int>{};
bool simplifyNames = false;
void main(List<String> arguments) {
arguments = arguments.toList();
simplifyNames = arguments.remove('--simplify');
var allCode = arguments.remove('--all');
Scrape()
// The number of levels of nesting active when each argument appears.
..addHistogram('Nesting depth')
// The number of levels of nesting active when each argument appears without
// counting lists.
..addHistogram('Ignoring lists')
// The number of levels of "child" or "children" named parameter nesting
// when each argument appears.
..addHistogram('Child nesting depth')
..addHistogram('Argument names')
// Strings that describe the structure of each nested argument.
..addHistogram('Argument nesting')
..addVisitor(() => NestingVisitor(allCode: allCode))
..runCommandLine(arguments);
var methods = buildMethods.keys.toList();
methods.sort((a, b) => buildMethods[b].compareTo(buildMethods[a]));
for (var method in methods) {
print('${buildMethods[method].toString().padLeft(3)}: $method');
}
print('${buildMethods.length} build() methods');
}
class NestingVisitor extends ScrapeVisitor {
final List<String> _stack = [];
final bool _allCode;
bool _pushed = false;
int _deepestNesting = 0;
NestingVisitor({bool allCode}) : _allCode = allCode ?? false;
@override
void beforeVisitBuildMethod(Declaration node) {
_deepestNesting = 0;
}
@override
void afterVisitBuildMethod(Declaration node) {
var startLine = lineInfo.getLocation(node.offset).lineNumber;
buildMethods['$path:$startLine'] = _deepestNesting;
}
@override
void visitArgumentList(ArgumentList node) {
// Only argument lists with trailing commas get indentation.
if (node.arguments.isNotEmpty &&
node.arguments.last.endToken.next.type == TokenType.COMMA) {
String name;
var parent = node.parent;
if (parent is MethodInvocation) {
name = parent.methodName.name;
} else if (parent is InstanceCreationExpression) {
name = parent.constructorName.toString();
} else if (parent is SuperConstructorInvocation) {
name = 'super.${parent.constructorName}';
} else {
name = '?(${parent.runtimeType})?';
}
if (simplifyNames) {
name = '';
}
for (var argument in node.arguments) {
var argName =
argument is NamedExpression ? argument.name.label.name : '';
if (_allCode || isInFlutterBuildMethod) {
record('Argument names', argName);
}
if (simplifyNames && argName != 'child' && argName != 'children') {
argName = '_';
}
_push('$name($argName:');
argument.accept(this);
_pop();
}
} else {
node.visitChildren(this);
}
}
@override
void visitBlock(Block node) {
var isFunction = node.parent is BlockFunctionBody;
if (!isFunction) _push('{');
node.visitChildren(this);
if (!isFunction) _pop();
}
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_push('=>');
node.visitChildren(this);
_pop();
}
@override
void visitFunctionExpression(FunctionExpression node) {
var isDeclaration = node.parent is FunctionDeclaration;
if (!isDeclaration) _push('(){');
node.visitChildren(this);
if (!isDeclaration) _pop();
}
@override
void visitListLiteral(ListLiteral node) {
for (var element in node.elements) {
_push('[');
element.accept(this);
_pop();
}
}
@override
void visitSetOrMapLiteral(SetOrMapLiteral node) {
for (var element in node.elements) {
_push('{');
element.accept(this);
_pop();
}
}
void _push(String string) {
_stack.add(string);
_pushed = true;
_deepestNesting = math.max(_deepestNesting, _stack.length);
}
void _pop() {
if (_pushed && (_allCode || isInFlutterBuildMethod)) {
record('Argument nesting', _stack.join(' '));
record('Nesting depth', _stack.length);
record('Ignoring lists', _stack.where((s) => s != '[').length);
record('Child nesting depth',
_stack.where((s) => s.contains('child')).length);
}
_pushed = false;
_stack.removeLast();
}
}