Skip to content

Instantly share code, notes, and snippets.

@drFabio
Last active February 9, 2022 12:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drFabio/becb947ab3b37f53fc30eda563c8b8ae to your computer and use it in GitHub Desktop.
Save drFabio/becb947ab3b37f53fc30eda563c8b8ae to your computer and use it in GitHub Desktop.
Adds props to objects using jsCodeShift
export default (fileInfo, api, options) => {
const { path: rawPath, value, declarator = "theme" } = options;
if (!rawPath || !value) {
console.error("Pass paths AND value to change the code ");
process.exit(1);
}
const paths = rawPath.split(".");
const j = api.jscodeshift;
const root = j(fileInfo.source);
const possibleRoots = root.find(j.VariableDeclarator, {
id: { name: declarator },
});
const rootNode = possibleRoots.filter((node) => {
return node.parentPath.get(0).value.type === "VariableDeclarator";
});
if (!rootNode.length && possibleRoots.length) {
console.warn(
`${declarator} was found but none had the VariableDeclarator so we ruled it out i.e const ${declarator} = {}`
);
process.exit(1);
}
// If we can't find the initial declarator then there is no point on proceeding
if (!rootNode.length) {
console.error(
`${declarator} not found. we couldn't find a variable with it's value`
);
process.exit(1);
}
let current = rootNode;
let found = false;
let enteredRootDeclarator = false;
// try to find as deep as a path as we can
do {
const name = paths[0];
const newCollection = current
.find(j.Property, { key: { name } })
.filter((obj, index) => {
return isDirectChildOfObject(current.get(0).node, obj);
});
if (!newCollection.length) {
found = false;
// at this point we found a partial match so we will continue the rest
console.log(`Not found we will enrich adding ${paths.join(".")}`);
continue;
}
found = true;
current = newCollection;
enteredRootDeclarator = true;
paths.shift();
} while (found && paths.length);
// we never entered the path we need to get the properties of the initial object.
// this means the value is totally new
if (!enteredRootDeclarator) {
current = rootNode.find(j.ObjectExpression).filter((obj, index) => {
return index === 0;
});
}
current.replaceWith((nodePath) => {
const { node } = nodePath;
const { length } = paths;
if (!length) {
//We have an exact match, need to replace it
node.value = j.literal(value);
return node;
}
// From here we need to traverse the remainder of the paths and add it until we can add the literal
let previous = enteredRootDeclarator ? node.value : node;
// here we are adding the remainder of not found paths
paths.forEach((remainder, index) => {
const isLast = length - 1 === index;
const object = j.property(
"init",
j.identifier(remainder),
isLast ? j.literal(value) : j.objectExpression([])
);
previous.properties.push(object);
previous = object.value;
});
return node;
});
return root.toSource();
};
function isDirectChildOfObject(parent, child) {
let current = child.parentPath;
// Ignoring object expressions so we can find nested object
while (current.node && current.node.type === "ObjectExpression") {
current = current.parentPath;
}
return current.node === parent;
}
@drFabio
Copy link
Author

drFabio commented Aug 24, 2020

A JS Codeshift codemod

Given a file like:

// menu.js
const menu = {
  food: {
    hamburger: 10,
    pizza: "unavailable",
  },
  beverages: {
    soft: {
      water: 2,
    },
    alcoholic: {
      beer: {
        ipa: {
          someIpa: 5,
        },
      },
    },
  },
};

You can execute the following command: (-d for dry run, -p for print)

jscodeshift -t addToObject.codemod.js  menu.js  -d -p  --path food.hotDog --value 10 --declarator menu

This will add hotDog with value of 10 to food

jscodeshift -t addToObject.codemod.js  menu.js  -d -p  --path beverages.alcoholic.gin.beefEater --value 5 --declarator menu

This will add a new gin object inside alcoholic with a value of {beefEater:5}

jscodeshift -t addToObject.codemod.js  menu.js  -d -p  --path desserts.cakes.chocolate  --value 3 --declarator menu

That will add a new category called desserts with an object cakes, with a value of choclate:3.

PS: Remember the amazing AST Explorer to figure out what to shift
For a good tutorial check the toptal one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment