Skip to content

Instantly share code, notes, and snippets.

@munificent
Created February 1, 2023 00:44
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/e03728874aae4d16a9760f207aecb16c to your computer and use it in GitHub Desktop.
Save munificent/e03728874aae4d16a9760f207aecb16c to your computer and use it in GitHub Desktop.
Analyze a Dart corpus for uses of "_" as an identifier
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:scrape/scrape.dart';
final wildcardPattern = RegExp(r'^_+$');
final typeNamePattern = RegExp(r'^_*[$A-Z]');
void main(List<String> arguments) {
Scrape()
..addHistogram('Declaration')
..addHistogram('Use')
..addVisitor(() => WildcardVisitor())
..runCommandLine(arguments);
}
class WildcardVisitor extends ScrapeVisitor {
final Set<Token> _visited = {};
final Set<Token> _unvisited = {};
@override
void visitCompilationUnit(CompilationUnit node) {
_visited.clear();
_unvisited.clear();
// Collect every wildcard token in the file. This way, we make sure that we
// find every single one in the parser.
var token = startToken;
while (token.type != TokenType.EOF) {
if (token.type == TokenType.IDENTIFIER &&
wildcardPattern.hasMatch(token.lexeme)) {
_unvisited.add(token);
}
token = token.next!;
}
super.visitCompilationUnit(node);
// Validate that we didn't miss any wildcards.
if (_unvisited.isNotEmpty) {
log('$path missing:');
for (var token in _unvisited) {
printToken(token);
}
}
}
@override
void visitAssignmentExpression(AssignmentExpression node) {
var target = node.leftHandSide;
if (target is SimpleIdentifier) {
_checkWildcard(target.token, 'Use', 'Assignment target');
}
super.visitAssignmentExpression(node);
}
@override
void visitCatchClauseParameter(CatchClauseParameter node) {
_checkWildcard(node.name, 'Declaration', 'Catch parameter');
super.visitCatchClauseParameter(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
_checkWildcard(node.name, 'Declaration', 'Constructor name');
_checkConstructorName(node.redirectedConstructor, 'Use',
'Factory constructor redirecting to private name');
super.visitConstructorDeclaration(node);
}
@override
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
_checkWildcard(node.fieldName.token, 'Use', 'Field initializer');
super.visitConstructorFieldInitializer(node);
}
@override
void visitDefaultFormalParameter(DefaultFormalParameter node) {
super.visitDefaultFormalParameter(node);
}
@override
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
_checkWildcard(node.name, 'Declaration', 'Enum value name');
_checkWildcard(node.arguments?.constructorSelector?.name.token, 'Use',
'Private constructor invocation');
super.visitEnumConstantDeclaration(node);
}
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
_checkWildcard(node.name, 'Declaration', 'Extension name');
super.visitExtensionDeclaration(node);
}
@override
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
_checkWildcard(node.loopVariable.name, 'Declaration', 'Loop variable');
super.visitForEachPartsWithDeclaration(node);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
for (var variable in node.fields.variables) {
_checkWildcard(variable.name, 'Declaration',
node.isStatic ? 'Static field' : 'Instance field');
}
super.visitFieldDeclaration(node);
}
@override
void visitFieldFormalParameter(FieldFormalParameter node) {
super.visitFieldFormalParameter(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_checkWildcard(node.name, 'Declaration', 'Function name');
super.visitFunctionDeclaration(node);
}
@override
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
super.visitFunctionTypedFormalParameter(node);
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
_checkConstructorName(
node.constructorName, 'Use', 'Private constructor invocation');
super.visitInstanceCreationExpression(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
_checkWildcard(node.name, 'Declaration', 'Method name');
super.visitMethodDeclaration(node);
}
@override
void visitMethodInvocation(MethodInvocation node) {
var target = node.target;
if (target is SimpleIdentifier && typeNamePattern.hasMatch(target.name)) {
_checkWildcard(
node.methodName.token, 'Use', 'Private constructor invocation');
}
super.visitMethodInvocation(node);
}
@override
void visitNamedType(NamedType node) {
var name = node.name;
if (name is SimpleIdentifier) {
_checkWildcard(name.token, 'Use', 'Type annotation');
} else {
_checkWildcard((name as PrefixedIdentifier).prefix.token, 'Use',
'Type annotation prefix');
_checkWildcard((name as PrefixedIdentifier).identifier.token, 'Use',
'Prefixed type annotation');
}
super.visitNamedType(node);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
_checkWildcard(node.constructorName?.token, 'Use',
'Redirection to private constructor');
super.visitRedirectingConstructorInvocation(node);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
_checkWildcard(node.token, 'Use', 'Identifier expression');
}
@override
void visitSimpleFormalParameter(SimpleFormalParameter node) {
_checkWildcard(node.name, 'Declaration', 'Parameter name');
super.visitSimpleFormalParameter(node);
}
@override
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
_checkWildcard(node.constructorName?.token, 'Use',
'Private superclass constructor invocation');
super.visitSuperConstructorInvocation(node);
}
@override
void visitSuperFormalParameter(SuperFormalParameter node) {
super.visitSuperFormalParameter(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
for (var variable in node.variables.variables) {
_checkWildcard(variable.name, 'Declaration', 'Top level variable');
}
super.visitTopLevelVariableDeclaration(node);
}
@override
void visitTypeParameter(TypeParameter node) {
_checkWildcard(node.name, 'Declaration', 'Type parameter');
super.visitTypeParameter(node);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
_checkWildcard(node.name, 'Declaration',
'Unknown variable declaration ${node.parent!.parent.runtimeType}');
super.visitVariableDeclaration(node);
}
@override
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
for (var variable in node.variables.variables) {
_checkWildcard(variable.name, 'Declaration', 'Local variable');
}
super.visitVariableDeclarationStatement(node);
}
void _checkConstructorName(
ConstructorName? name, String category, String type) {
if (name == null) return;
_checkWildcard(name.name?.token, category, type);
var typeName = name.type.name;
if (typeName is PrefixedIdentifier) {
_checkWildcard(typeName.identifier.token, category, type);
}
}
void _checkWildcard(Token? token, String category, String type) {
if (token == null) return;
if (!wildcardPattern.hasMatch(token.lexeme)) return;
// Don't visit the same node multiple times. If a higher level AST node
// has already categorized it, we're done.
if (_visited.contains(token)) return;
record(category, type);
_unvisited.remove(token);
_visited.add(token);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment