In the last issue, we discussed the challenges of maintaining feature toggles in a large codebase. Today, we’ll explore an efficient solution to automate the removal of feature toggles using codemods. This approach not only saves time but also reduces the risk of errors and ensures consistency across the codebase.
How Could We Do It?
Manually removing feature toggles from a large application is not feasible. It’s a time-consuming, error-prone, and labor-intensive process. Additionally, as we make progress, new usages may be added, making it a never-ending chase. Therefore, automating this process is essential.
Automation involves three key steps:
Analyze the Patterns: Identify patterns manually.
Record the Steps: Note down the steps if done manually.
Write a Script: Create a script to automate these steps.
Let's dive into how to write an automation script using traditional methods like regular expressions and then move to a more robust approach using Abstract Syntax Trees (ASTs) and jscodeshift.
Regular Expressions and Limitations
Using regular expressions with tools like awk
or sed
, we can transform specific code patterns. For instance, the following sed
command changes a conditional assignment:
sed -E 's/(const [^=]+ = )featureToggle\\([^)]+\\) \\? (\\{[^}]+\\}) : \\{\\};/\\1\\2;/g' input.js > output.js
This works for specific cases but falls short when dealing with slight variations in code. Regular expressions can become complex and hard to maintain, especially with changes in code formatting or structure.
The Power of Abstract Syntax Trees (ASTs)
To overcome these limitations, we parse the source code into a structured data format called an Abstract Syntax Tree (AST). This allows us to traverse, access, and modify the code structure programmatically. ASTs provide a more reliable and maintainable way to transform code compared to text-based manipulations.
For example, parsing the following code snippet:
const data = featureToggle('feature-x') ? { foo: 'bar' } : {};
Produces an AST, which we can then manipulate to change the structure while preserving syntactic correctness.
Introducing jscodeshift
💡 jscodeshift is a toolkit for running codemods on JavaScript and TypeScript codebases. It uses the Abstract Syntax Tree (AST) to parse and transform code, allowing developers to automate code changes safely and efficiently. With jscodeshift, you can search for specific code patterns and apply transformations, making it ideal for large-scale refactoring tasks.
Using jscodeshift, we can create a transform script to handle feature toggles. The process is similar to manipulating the DOM: find elements that match certain criteria and apply changes.
Example Transformation with jscodeshift
Consider the function call featureToggle('feature-x')
. We can search for all CallExpression
nodes that match this pattern and apply transformations:
function transformer(fileInfo, api) {
const j = api.jscodeshift;
return j(fileInfo.source)
.find(j.CallExpression, {
callee: {
type: 'Identifier',
name: 'featureToggle'
},
arguments: [
{
type: 'Literal',
value: 'feature-x'
}
]
})
.forEach(path => {
console.log(path.node.value); // Transform the AST node as needed
})
.toSource();
}
We can run this transform using the jscodeshift CLI:
jscodeshift -t transform.js target-codebase/
This command searches for matching nodes and prints them, providing a starting point for further transformations.
Implementing the If Statement Transformation
Next, we implement a transformation for if statements:
root
.find(j.IfStatement, {
test: {
type: "CallExpression",
callee: {
type: "Identifier",
name: "featureToggle",
},
arguments: [
{
type: "Literal",
value: 'feature-x',
},
],
},
})
.forEach((path) => {
if (path.node.consequent.type === "BlockStatement") {
j(path).replaceWith(path.node.consequent.body);
}
});
This replaces the if statement with its body, removing the feature toggle check.
The Conditional Statement Transformation
We also handle conditional statements:
const data = featureToggle('feature-x') ? { foo: 'bar' } : {};
Get rid of the unused imports and function
Also, we need to consider the post-transformation, we should clean up any unused code once the function call featureToggle
is removed. The import statement, the old implementation should go.
import {featureToggle} from './utils/featureToggle';
const convertOld = (input: string) => {
return input.toLowerCase();
}
const convertNew = (input: string) => {
return input.toUpperCase();
}
const result = featureToggle('feature-convert-new') ? convertNew("Hello, world") : convertOld("Hello, world");
console.log(result);
After applying the codemod:
const convertNew = (input: string) => {
return input.toUpperCase();
}
const result = convertNew("Hello, world");
console.log(result);
You can find the complete code in this GitHub repository. Additionally, watch this video tutorial for a detailed walkthrough of using jscodeshift.
Bonus: Composition of Transforms
While you can implement all logic in one transform, it's better to break them into smaller, composable transforms. For example:
Remove unused function declarations
Remove a specific function usage
These small steps can be tested separately and chained together to create a comprehensive transform.
Summary
Automating code transformations with jscodeshift offers a powerful way to handle repetitive and error-prone tasks like removing feature toggles. By breaking down the process into smaller, manageable steps, we can create robust and reusable transformations. Stay tuned for more insights and practical tips on maintaining clean and efficient codebases.
Share Your Stories
I’d love to hear from you! Have you faced similar challenges with feature toggles or other code maintenance tasks? Share your stories or any obstacles you're encountering. Your experiences could help others in the community, and together we can find more solutions to common problems. Feel free to leave comments or notes to join the discussion. Let's keep the conversation going and support each other in our coding journeys!