How do I use the Dart analyzer class to build a tree instance that reflects the AST?

167 views Asked by At

I would like to use the analyzer package to parse the widget tree that's defined in the build function and automagically factor the code for each method invocation into separate methods.

Pretend the sample input below contains a massive widget tree that needs to be decomposed, ideally based on the child and children properties. In the example, I'd like to navigate into the MyApp class declaration, into the build method declaration, into the return MaterialApp... return statement and then to each named expression in that function, such as theme, home, etc.

However, I'm not sure how to constrain the AstVisitor in such a way that allows me to follow the AST down a specific path and not just visit all the nodes in the tree for that type. The AstVisitor documentation also doesn't guarantee the order that it walks the tree, meaning I can reconstruct the widget tree in a deterministic way.

Am I going about this all wrong?

Sample input:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(title: const Text('Foo Bar Baz')),
          body: null,
        ));
  }
}

My code:

class _BuildMethodVisitor extends RecursiveAstVisitor<UINode> {
  final UINode root;

  _BuildMethodVisitor(this.root) : super();

  @override
  UINode? visitClassDeclaration(ClassDeclaration node) {
    print('classDeclaration::${node.name}');

    super.visitClassDeclaration(node);

    return null;
  }

  @override
  UINode? visitReturnStatement(ReturnStatement node) {
    print('returnStatement::${node}');

    super.visitReturnStatement(node);

    return null;
  }

  @override
  UINode? visitNamedExpression(NamedExpression node) {
    print('namedExpression::${node}');

    super.visitNamedExpression(node);

    return null;
  }

  @override
  UINode? visitMethodInvocation(MethodInvocation node) {
    print('methodInvocation::${node}');

    super.visitMethodInvocation(node);

    return null;
  }
}
1

There are 1 answers

0
Randal Schwartz On

The simplest interface for that is probably a custom_lint with a code fix. Remi put a lot of work on making lints and refactorings easier and workarounds for things that were not as well designed. You might check that out in your pursuits.