22nd May 2021
3 mins

Writing a Python Slayer in Javascript using AST

#AST#NodeJs#Syntax Tree

While writing code in a collaborative environment, we all face issues where certain code practices were not followed, or we decided to move from one practice to another one or It could be that one guy who always forgets which variable casing to use. All of above are very frequently occurring scenarios and the solution to that is very simple, yet very useful.

If you have read my previous blog then you would be knowing that from past blog I am bragging about my learnings on AST. So in this blog I will continue my bragging and will tell you that How you can write a NodeJs script to analyse and modify the code using Syntax Trees.

To give you an example of the problem look at the code below-

Example really bad code, works but bad code

Above code is taken from one of my open source project. And I have edited the code to show you the problem. As you might be able to find it by now that all the variables declared in the code are not camel cased instead they are snake cased(or python cased 🐍). And now we have a task to change all the variable declaration to camel casing by slaying some pythons 🤺 (And yeah no animals were harmed while writing the code other than the writer himself).

One of the possible solution that comes into mind is to open the file in a text editor and start renaming variables but believe me it is a cumbersome task that takes lot of efforts and time. And wouldn’t it be nice if there was something that can understand the code the way you are understanding, means something that can distinguish between an identifier and a value. Let me tell you loud and clear, again- won’t be writing the line, if it wasn’t.

So let’s get into action and slay some Python -

Assuming that above code is available to our script as String in a variable (You can get your code in the script by using simple file read).

const estraverse = require('estraverse');
const escodegen = require('escodegen');
const esprima = require('esprima');

const fs = require('fs');
const path = require('path');

const sourcePath = process.argv[2];
const destinationPath = process.argv[3];

const snakeToCamel = (str) => str.replace(/([-_][a-z])/g,(group) => group.toUpperCase().replace('-', '').replace('_', ''));

const file = fs.readFileSync(path.resolve(__dirname, sourcePath), {encoding: 'UTF-8'});

const ast =  esprima.parseScript(file);

estraverse.traverse(ast, {
	enter: function (node, parent) {
		if (node.type == 'Identifier' && node.name.includes('_')) {
			node.name = snakeToCamel(node.name);
			return node;
		}
	}
});

var jsCode = escodegen.generate(ast);

fs.writeFileSync(path.resolve(__dirname, destinationPath),jsCode);

Time for a little explanation-

  • line number 13: get the code into a variable called file using nodejs fs module.
  • line number 15: takes the code in the string format and builds AST for the code. Generated AST looks something like this —
visualising generated AST
  • line number 17: now with help of estraverse, we can visit the nodes of the tree and when a node is an Identifier and has underscore , we will call a function snakeToCamel() to change the name of the variable and return the modified node. Note that after visiting all the nodes and performing modification, our syntax tree will have all the identifier with camel cased names only. (🐍=> ❌, 🐫 => ).
  • line number 26: as we have our modified AST with us, we can use escodegen to generate back the code.
  • line number 28: writing back the generated code to the file.

And tada…, we just freed our beautiful javascript code from the grips of a python.

In the next post of the series, we will figure out how we can use AST to write es-lint rules by writing a eslint reporter for the const identifier in lowercase. Thanks for reading, that all for now. Links to other blogs in the series -

  1. (Prev) Into the world of Abstract Syntax Tree
  2. (Next) Creating your very own custom ESLint plugin
  3. (Next) Dynamically generating code using AST

…, Over and Out.


Attributions:

Photo by Stillness InMotion on Unsplash