Skip to content

Instantly share code, notes, and snippets.

@munificent
Created December 3, 2021 00:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save munificent/58a73182ca3aee6ed37a06ca2f33fc63 to your computer and use it in GitHub Desktop.
Save munificent/58a73182ca3aee6ed37a06ca2f33fc63 to your computer and use it in GitHub Desktop.
Script to analyze constructors and fields in Dart classes.
// Copyright (c) 2021, 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 'package:analyzer/dart/ast/ast.dart';
import 'package:scrape/scrape.dart';
void main(List<String> arguments) {
Scrape()
..addHistogram('Constructor type')
..addHistogram('Constructor name')
..addHistogram('Non-factory body')
..addHistogram('Factory body')
..addHistogram('Super initializer')
..addHistogram('Constructor parameters')
..addHistogram('Constructor count', order: SortOrder.numeric)
..addHistogram('Field initialization')
..addHistogram('Field mutability')
..addHistogram('Fields in initializer lists')
..addVisitor(() => ConstructorVisitor())
..runCommandLine(arguments);
}
class ConstructorVisitor extends ScrapeVisitor {
final Map<String, Set<String>> fields = {};
int constructorCount = 0;
@override
void visitClassDeclaration(ClassDeclaration node) {
fields.clear();
constructorCount = 0;
super.visitClassDeclaration(node);
// See how the fields were initialized.
fields.forEach((name, initializer) {
var sorted = initializer.toList()..sort();
if (sorted.isEmpty) {
record('Field initialization', 'not initialized');
} else {
record('Field initialization', sorted.join(', '));
}
});
record('Constructor count', constructorCount);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
// Only care about instance fields.
if (node.isStatic) return;
var mutability = [
if (node.fields.isLate) 'late',
if (node.fields.isFinal) 'final'
];
if (mutability.isEmpty) {
record('Field mutability', 'var');
} else {
record('Field mutability', mutability.join(' '));
}
for (var field in node.fields.variables) {
if (field.initializer != null) {
fields.putIfAbsent(field.name.name, () => {}).add('at declaration');
} else {
fields.putIfAbsent(field.name.name, () => {});
}
}
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
constructorCount++;
if (node.constKeyword != null) {
if (node.factoryKeyword != null) {
record('Constructor type', 'const factory');
} else {
record('Constructor type', 'const');
}
} else {
if (node.factoryKeyword != null) {
record('Constructor type', 'non-const factory');
} else {
record('Constructor type', 'non-const');
}
}
if (node.name != null) {
record('Constructor name', 'named');
} else {
record('Constructor name', 'unnamed');
}
var hasPositional = false;
var hasOptional = false;
var hasNamed = false;
for (var parameter in node.parameters.parameters) {
if (parameter.isRequiredPositional) hasPositional = true;
if (parameter.isOptionalPositional) hasOptional = true;
if (parameter.isNamed) hasNamed = true;
if (parameter is DefaultFormalParameter) {
parameter = parameter.parameter;
}
if (parameter is FieldFormalParameter) {
fields
.putIfAbsent(parameter.identifier.name, () => {})
.add('`this.` parameter');
}
}
var sections = [
if (hasPositional) 'positional',
if (hasOptional) 'optional positional',
if (hasNamed) 'named'
];
if (sections.isEmpty) {
record('Constructor parameters', 'no parameters');
} else if (sections.length == 1) {
record('Constructor parameters', 'only ${sections.first} parameters');
} else {
record('Constructor parameters',
'both ${sections.join(" and ")} parameters');
}
if (node.initializers.isNotEmpty) {
var hasSuper = false;
for (var initializer in node.initializers) {
if (initializer is SuperConstructorInvocation) {
hasSuper = true;
} else if (initializer is ConstructorFieldInitializer) {
fields
.putIfAbsent(initializer.fieldName.name, () => {})
.add('initializer list');
var expr = initializer.expression;
if (expr is SimpleIdentifier &&
'_${expr.name}' == initializer.fieldName.name) {
record('Fields in initializer lists', '_field = field');
} else if (expr is NullLiteral ||
expr is BooleanLiteral ||
expr is IntegerLiteral ||
expr is DoubleLiteral ||
expr is StringLiteral) {
record('Fields in initializer lists', 'field = literal');
} else {
record('Fields in initializer lists', 'other');
}
}
}
if (hasSuper) {
record('Super initializer', 'has super()');
} else {
record('Super initializer', 'none');
}
}
var bodyPrefix = node.factoryKeyword == null ? 'Non-factory' : 'Factory';
var body = node.body;
if (body is EmptyFunctionBody) {
record('$bodyPrefix body', ';');
} else if (body is ExpressionFunctionBody) {
record('$bodyPrefix body', '=>');
} else if (body is BlockFunctionBody) {
if (body.block.statements.isEmpty) {
record('$bodyPrefix body', '{} (empty)');
} else {
record('$bodyPrefix body', '{...}');
}
} else {
record('$bodyPrefix body', 'WTF');
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment