Created
December 3, 2021 00:15
-
-
Save munificent/58a73182ca3aee6ed37a06ca2f33fc63 to your computer and use it in GitHub Desktop.
Script to analyze constructors and fields in Dart classes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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