Skip to content

Instantly share code, notes, and snippets.

@cnnrrss
Last active February 5, 2026 23:25
Show Gist options
  • Select an option

  • Save cnnrrss/a9aad067326b0c98e3fd9b710685bdab to your computer and use it in GitHub Desktop.

Select an option

Save cnnrrss/a9aad067326b0c98e3fd9b710685bdab to your computer and use it in GitHub Desktop.
/**
Usage:
npx jscodeshift --extensions=js,jsx -t scripts/remove-autobind-codemod.js app/... 2>&1
*/
const { parse } = require("@babel/parser");
const parser = {
parse(source) {
return parse(source, {
sourceType: "module",
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
startLine: 1,
tokens: true,
plugins: [
"jsx",
"asyncGenerators",
"bigInt",
"classPrivateMethods",
"classPrivateProperties",
"classProperties",
["decorators", { decoratorsBeforeExport: true }],
"doExpressions",
"dynamicImport",
"exportDefaultFrom",
"exportNamespaceFrom",
"functionBind",
"functionSent",
"importMeta",
"logicalAssignment",
"nullishCoalescingOperator",
"numericSeparator",
"objectRestSpread",
"optionalCatchBinding",
"optionalChaining",
"partialApplication",
"throwExpressions",
"topLevelAwait",
],
});
},
};
module.exports = function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
let hasChanges = false;
root.find(j.ClassDeclaration).forEach((classPath) => {
const classNode = classPath.node;
if (!classNode.decorators) return;
const autobindDecoratorIndex = classNode.decorators.findIndex(
(d) => d.expression && d.expression.name === "autobind"
);
if (autobindDecoratorIndex === -1) return;
classNode.decorators.splice(autobindDecoratorIndex, 1);
if (classNode.decorators.length === 0) {
classNode.decorators = null;
}
hasChanges = true;
classNode.body.body.forEach((member, index) => {
if (member.type !== "ClassMethod") return;
if (member.static) return;
if (member.kind !== "method") return;
if (member.key.name === "constructor") return;
if (member.key.name === "render") return;
if (member.key.name?.startsWith("component")) return;
if (member.key.name?.startsWith("UNSAFE_")) return;
if (member.key.name === "shouldComponentUpdate") return;
if (member.key.name === "getSnapshotBeforeUpdate") return;
if (member.key.name?.startsWith("getDerivedState")) return;
const arrowFunc = j.arrowFunctionExpression(member.params, member.body, false);
arrowFunc.async = member.async;
const classProp = j.classProperty(member.key, arrowFunc);
classProp.static = false;
classNode.body.body[index] = classProp;
});
});
root.find(j.ClassMethod).forEach((methodPath) => {
const methodNode = methodPath.node;
if (!methodNode.decorators) return;
const autobindDecoratorIndex = methodNode.decorators.findIndex(
(d) => d.expression && d.expression.name === "autobind"
);
if (autobindDecoratorIndex === -1) return;
methodNode.decorators.splice(autobindDecoratorIndex, 1);
if (methodNode.decorators.length === 0) {
methodNode.decorators = null;
}
hasChanges = true;
if (methodNode.kind !== "method") return;
if (methodNode.key.name === "constructor") return;
if (methodNode.key.name === "render") return;
if (methodNode.key.name?.startsWith("component")) return;
if (methodNode.key.name?.startsWith("UNSAFE_")) return;
const arrowFunc = j.arrowFunctionExpression(methodNode.params, methodNode.body, false);
arrowFunc.async = methodNode.async;
const classProp = j.classProperty(methodNode.key, arrowFunc);
classProp.static = methodNode.static;
const classBody = methodPath.parent.node;
const index = classBody.body.indexOf(methodNode);
if (index !== -1) {
classBody.body[index] = classProp;
}
});
if (hasChanges) {
root.find(j.ImportDeclaration).forEach((importPath) => {
if (importPath.node.source.value === "autobind-decorator") {
const autobindUsages = root.find(j.Identifier, { name: "autobind" });
const isUsed = autobindUsages.some((path) => {
return (
path.parent.node.type === "Decorator" ||
(path.parent.node.type === "CallExpression" &&
path.parent.parent.node.type === "Decorator")
);
});
if (!isUsed) {
j(importPath).remove();
}
}
});
}
return hasChanges ? root.toSource({ quote: "double" }) : null;
};
module.exports.parser = parser;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment