Table of Contents

1. Sample babel transform to turn snake_case into camelCase

First we write a simple function that takes a snake_case string and turns it into camel case:

function snakeToCamel(str) {
  const [first, ...rest] = str.split("_");
  return `${first}${rest.map(capitalize).join("")}`;
}

function capitalize([v, ...vs]) {
  return `${v.toLocaleUpperCase()}${vs.join("")}`;
}

Next, we define our babel plugin. We don't need any direct dependency on babel, since babel injects its functionality as an argument to the plugin. Here, we're accessing @babel/types from the argument passed to our function and then creating a visitor that handles each Identifier node. For each node, we rename it using babel's scope-handling functions, which handles most of the cases we care about, except for toplevel definitions like . To handle those, we replace the path with a camel-cased version of the node.

module.exports = function camelCasingVisitor({ types: t }) {
  return {
    visitor: {
      Identifier(path) {
        if (isSnakeCased(path.node)) {
          path.scope.rename(path.node.name, snakeToCamel(path.node.name));
          path.replaceWith(t.identifier(snakeToCamel(path.node.name)));
        }
      },
    },
  };
};

function isSnakeCased(node) {
  return /_/.test(node.name)
}

2. Sample results

Given this plugin, if we create a new babel project that includes it (relative path here because it's in the same project):

{
  "compact": false,
  "retainLines": true,
  "plugins": ["../src/index"]
}

Then, given some javascript with snake_case identifiers:

import { a_thing } from "some_unconventional_package";

const my_constant = a_thing(4);

function some_function_stuff() {
  let my_local_variable;
  return function inner_function() {
    const other_local_variable = 1;
    return my_local_variable + other_local_variable + "!";
  };
}

// Renaming this one is sort of dangerous 🤔
undeclared_variable = 234;

We get this output:

import { aThing } from "some_unconventional_package";

const myConstant = aThing(4);

function someFunctionStuff() {
  let myLocalVariable;
  return function innerFunction() {
    const otherLocalVariable = 1;
    return myLocalVariable + otherLocalVariable + "!";
  };
}

// Renaming this one is sort of dangerous 🤔
undeclaredVariable = 234;

3. Limitations

3.1. Renaming undeclared globals is problematic

the undeclared_variable example above is a bit problematic: with this transform, there's no way to access names you don't control that might exist in the global environment of your code. We could add some sort of comment directive for this, but it would mar the aesthetics of the demo. We could also just leave them alone, but that would mar the aesthetics too.

3.2. Name clashes possible

The names don't always manage to avoid collisions, if a matching camelCase identifier already exists. For example this code:

const a_name = 'foo'
function aName() {}

// This will be renamed to aName and clash with the function
a_name.toLower();

Produces this result:

const aName = 'foo';
function aName() {}

// This will be renamed to aName and clash with the function
aName.toLower();

Author: Edward Langley

Created: 2022-03-31 Thu 20:27

Validate