Auditing a React Application with Abstract Syntax Trees
Maintaining and refactoring a large codebase requires lots of development time and effort. Issues, such as inconsistencies in variable naming or passing incorrect arguments in function calls, tend to happen more frequently in larger codebases. As you scan your codebase for these issues, you may observe some of them emerging repeatedly in different parts of your codebase. Manually sifting through tens and hundreds of files to apply the same set of changes in multiple places is simply not feasible. Anytime you have to manually make many repetitive changes, the likelihood of a mistake occurring increases. To automate this entire process, you need tooling that draws upon abstract syntax trees to audit code, report the findings and, if warranted, immediately resolve them. Abstract syntax trees are not only used for compilers; they are also present in other types of development tooling. By parsing source code into an abstract syntax tree, we can traverse the nodes of the tree to interact directly with each and every literal, identifier, etc. in the source code. For React applications, auditing with abstract syntax trees lets us understand the current state of our components' source code and get information about our components' contents. For example, if we notice <button /> elements scattered throughout our application, then we can write a tool to list the CSS classes assigned to their className attributes. Using this list, we can check if there are any incorrectly spelled CSS classes and/or invalid CSS classes and fix them. Additionally, we can refactor the <button /> elements into a <Button /> component and consolidate the CSS classes within this single component. Therefore, if we decide to rename/remove any of the CSS classes, then we only need to visit and edit one component instead of many. Writing development tooling based on abstract syntax trees takes little time thanks to Babel's large ecosystem of packages and plugins. Babel comes with packages for parsing source code ( @babel/parser ) and traversing abstract syntax trees ( @babel/traverse ). Below, I'm going to show you how to write an auditing tool for React applications using Babel. To get started, initialize a new Create React App project: For this tutorial, we will be writing an auditing tool that generates a report of the <button /> elements used within the React application. The report will list the attributes (i.e. type , className and onClick ) set on these elements and the values assigned to them. If the report shows many many <button /> elements sharing the same CSS classes, then we can refactor them into a single <Button /> component that renders a <button /> element with those CSS classes. Within the new project, create three components that contain <button /> elements with type , className and onClick attributes: ( src/components/Modal.tsx ) ( src/components/LoginForm.tsx ) ( src/components/Card.tsx ) While the components are not actively used within the application, you can still audit them since the auditing tool statically analyzes the components' source code. It does not run the React application. The auditing tool uses two Babel packages: Let's install these two packages: To find all the relevant component files, the auditing tool uses a glob pattern to search for files by their name. Let's install the glob package: Since this tool will be written with TypeScript, we will need to install ts-node to run it and type definition files for the glob and Babel packages. Note : @babel/parser provides its own type definitions. Therefore, you don't need to install any extra dependency for its type definitions. At the root of the project directory, create a new directory named audits . Within this directory, create a buttons-audit.ts file. Within this file, start by synchronously glob searching for the component files and reading their content. ( audits/buttons-audit.ts ) Add an npm script to package.json to execute this script: ( package.json ) Note : The module compiler option suppresses the warning message Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. . For larger projects, you should define compiler options within a tsconfig.json file. Run this npm script to verify that ts-node properly runs the audits/buttons-audit.ts file. Now, parse the source code of each file into an abstract syntax tree. ( audits/buttons-audit.ts ) Call the traverse function from the @babel/traverse package to traverse the abstract syntax tree. Pass it the abstract syntax tree generated by the parser and an object with the nodes to execute code when visited. In this case, we would like to execute code whenever Babel finds a <button /> JSX element. Since an element's attributes are assigned to its opening, not closing, tag, we should execute code on JSXOpeningElement nodes. A JSXOpeningElement node represents any opening JSX element (i.e. opening <a /> element tags), so how do we narrow down to opening <button /> element tags? Anytime Babel encounters a node of type JSXIdentifier with a name of button , this indicates that we have reached a node that represents an opening <button /> element tag. This is where we want to execute code to retrieve the <button /> element's attributes and corresponding values. ( audits/buttons-audit.ts ) To check whether an opening <button /> tag has the className attribute, we must iterate the node's attributes for a className attribute: ( audits/buttons-audit.ts ) If the <button /> element does not have a className attribute, then we should skip it. ( audits/buttons-audit.ts ) Next, record the button's attributes and their corresponding values to an object named propsAndValues , which we declare in the outer scope of the script: ( audits/buttons-audit.ts ) Once the audit finishes, we can log our findings. ( audits/buttons-audit.ts ) Altogether... ( audits/buttons-audit.ts ) If you re-run the audit, then you will see that there are four buttons with the CSS class btn . Since these buttons either have a btn--primary or btn--secondary CSS class, we may consider refactoring these buttons into a <Button /> component that centralizes these CSS classes in one file. If we ever decide to rename these CSS classes during a redesign, then we would only need to make the changes within this file only. For larger codebases, this approach allows you to quickly audit your code, saving you lots of development time that can be allocated towards other important tasks. Best of all, you can export this report into a separate file to share these results with other team members. Try auditing your own React applications with abstract syntax trees. You can also check out our new course, The newline Guide to Practical Abstract Syntax Trees , for more practical techniques you need to maintain any size codebase.