Last active
February 5, 2026 23:25
-
-
Save cnnrrss/a9aad067326b0c98e3fd9b710685bdab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| 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