Understanding a JavaScript Transformer Function for Automated Attribute Addition in JSX Elements

M Sahil Hussain
4 min readMar 18, 2024

--

In modern web development, automated code transformation is becoming increasingly prevalent. Tools like jscodeshift allow developers to programmatically manipulate JavaScript code, offering benefits such as code refactoring, migration, and enhancement. In this article, we’ll delve into a JavaScript transformer function designed to automate the addition of attributes to JSX elements. We’ll dissect the code, explain its functionality, and explore its practical applications.

The Problem

Consider a scenario where you need to add a data-test attribute to JSX elements in your codebase for testing purposes. Manually adding these attributes across multiple files can be tedious and error-prone, especially in large projects. This is where automation can significantly improve developer productivity and code consistency.

The Solution: jscodeshift

jscodeshift is a tool developed by Facebook for transforming JavaScript code programmatically. It allows developers to write scripts that can analyze and modify code en masse, making it ideal for tasks like adding attributes to JSX elements.

import { relative, sep } from "path";

// Define the transformer function, which receives fileInfo, api, and options
export default function transformer(fileInfo, api, options) {
// Import the jscodeshift library and initialize variables
const j = api.jscodeshift;
const root = j(fileInfo.source);
let sequence = 0; // Initialize sequence counter for data-test attribute values

// Get the relative file path and replace path separators with dashes
const filePath = relative(process.cwd(), fileInfo.path).replace(
new RegExp(sep.replace("\\", "\\\\"), "g"),
"-"
);
const fileName = filePath.replace(/\.\w+$/, ""); // Remove file extension

const dataTestPrefix = `${fileName}-`; // Prefix for data-test attribute values

// Function to create data-test attribute
const createDataTestAttribute = () =>
j.jsxAttribute(
j.jsxIdentifier("data-test"),
j.stringLiteral(`${dataTestPrefix}${sequence++}`)
);

// Predefined sets of tags to add attributes to
const allowedTags = new Set([
// Common HTML Tags
"div", "span", "p", "a", "img", "ul", "ol", "li", "table", "tr", "td", "th", "form", "input", "button", "label", "article",
// Material-UI (MUI) Tags
"AppBar", "Button", "Card", "TextField", "Checkbox", "Radio", "Select", "IconButton", "Typography", "Grid", "Snackbar", "Dialog",
"Tooltip", "Tab", "Tabs", "Slider", "Stepper", "Menu", "Backdrop", "Box",
]);

const styleComponentTags = new Set([
// Style Component Tags
"Input", "Btn", "Button", "Container", "Content", "Div", "Card", "Style", "Styles", "Styled", "ChangeIcon", "AddMore", "Custom",
"Value", "Cancel", "ModeratorImg", "Tag", "Title", "Panel", "Wrapper", "HashtagList", "HashtagItem", "Avatar", "Header", "Profile",
"Follower", "Setting", "Footer", "Error"
]);

// Check if the file is in an ignored folder
const ignoreFolders = options.ignoreFolders.split(",") || []; // Split ignoreFolders string into an array
const isInIgnoredFolder = ignoreFolders.some((folder) =>
fileInfo.path.includes(`${sep}${folder}${sep}`)
);
if (isInIgnoredFolder) {
return fileInfo.source; // Return original source if file is in ignored folder
}

// Find and transform JSX elements
root.find(j.JSXOpeningElement).forEach((path) => {
const elementName = path.node.name.name; // Get the name of the JSX element
// Check if the element name is in the allowed tags set or matches any style component tag (case insensitive)
if (allowedTags.has(elementName) ||
[...styleComponentTags].some(tag => elementName.match(new RegExp(tag, 'i')))) {
const attributes = path.node.attributes; // Get the attributes of the JSX element
// Check if the JSX element already has a data-test attribute
if (!attributes.some((attr) => attr.name && attr.name.name === "data-test")) {
attributes.push(createDataTestAttribute()); // Add data-test attribute if not already present
}
}
});

return root.toSource(); // Return the modified source code
}

Run this script on the terminal

Install jscodeshift: If you haven’t already installed jscodeshift globally,

npm install -g jscodeshift

then run this command

jscodeshift -t transform.js <path_to_your_files> --ignoreFolders="node_modules,build,dist,.expo,express-server,web-build" --parser=babel

Understanding the Code

Let’s dissect the provided jscodeshift script step by step:

  1. Setup and Initialization: The script starts by importing necessary modules and initializing variables.
  2. File Path Handling: The relative function from the path module is used to determine the relative path of the current file. This path is then transformed to replace path separators with dashes, forming the basis for the data-test attribute values.
  3. Creating Data-Test Attributes: The createDataTestAttribute function generates a data-test attribute with a unique value based on the file name and a sequence number. This ensures uniqueness across elements within a file.
  4. Defining Allowed Tags: HTML and Material-UI tags that are eligible for attribute addition are defined as sets. Sets offer faster lookup times compared to arrays, improving script performance.
  5. Ignoring Certain Folders: The script allows developers to specify folders to ignore via command-line options. Files within these folders will remain untouched by the transformation.
  6. Finding JSX Elements: Using jscodeshift’s find method, the script traverses JSX elements in the AST (Abstract Syntax Tree). It then checks if the element's tag name matches any of the allowed tags or style component tags.
  7. Adding Data-Test Attributes: If an element qualifies for attribute addition and doesn’t already have a data-test attribute, one is added using the createDataTestAttribute function.
  8. Returning Transformed Code: Finally, the modified AST is converted back to source code and returned.

Conclusion

By leveraging jscodeshift, developers can automate repetitive tasks, enhance code quality, and improve productivity. The provided script demonstrates how to efficiently add attributes to JSX elements, ensuring consistency and facilitating testing efforts across projects of any scale.

Incorporating automation tools like jscodeshift into your workflow empowers developers to focus on high-value tasks while minimizing manual labor. As projects evolve and grow, such optimizations become increasingly invaluable, fostering maintainable and scalable codebases.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

M Sahil Hussain
M Sahil Hussain

Written by M Sahil Hussain

Front End Developer at @MindZenX | 700+ DSA problems | 8+ Web Dev Project | 15+ Mobile UI | 12+ Web UI

No responses yet

Write a response