codemod / codemod/react-codemod
Tracking: adopt shared @jssg/utils helpers across react-codemod transforms
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3
- Forks
- 0
- Avg merge
- 4m
- Merged PRs (30d)
- 2
Description
Summary
After auditing all 26 working transforms in codemods/, a large amount of import-handling, line-removal, scope-walking, and indentation logic is reimplemented per codemod. Promoting a small set of shared helpers (some already in @jssg/utils, some currently internal to userland-migrations/utils) would let us delete an estimated ~1.5–2 kLOC of plumbing and make the transforms easier to write and maintain.
This issue tracks the rollout. Each util is broken out into its own actionable sub-task with the concrete codemods that benefit.
Utils under consideration
Already published in @jssg/utils:
getImportaddImportremoveImport
Currently internal in userland-migrations/utils/src/ast-grep (candidates to upstream):
get-scopeindent(detectIndentUnit,getLineIndent)remove-linesupdate-bindingremove-bindingresolve-binding-pathimport-statementrequire-callmodule-dependencies
Aggregate ranking
| Util | Codemods materially helped | Priority |
|---|---|---|
import-statement + require-call + module-dependencies |
18 / 26 | P0 |
getImport (extended for multi-source) |
20 / 26 (5 already use it) | P0 |
update-binding / remove-binding |
15 / 26 | P0 |
removeImport |
13 / 26 (2 already use it) | P1 |
addImport (with sorted-insertion heuristic) |
11 / 26 | P1 |
remove-lines (Edit-returning variant) |
11 / 26 | P1 |
resolve-binding-path |
10 / 26 | P2 |
get-scope (multi-kind stop list) |
10 / 26 | P2 |
indent (detectIndentUnit, getLineIndent) |
5 / 26 | P2 |
Recommendations
- P0 — Upstream
import-statement+require-call+module-dependenciesto@jssg/utils(or extendgetImportto acceptfrom: string[]and return all matches). Removes the copy-pasted ~40-linehasReact+importSource+requireSourcetriplet from at least 18 codemods. Bottleneck forreact-to-react-dom,react-native-view-prop-types,update-react-imports,replace-react-test-renderer-import, and theComponent/PureComponent-across-React-aliases finders. - P0 — Add
update-binding/remove-bindingto@jssg/utils.removeImportalready covers thedrop the whole specifierhalf, but the rename / merge / append / fall-back-to-side-effect-import logic is rebuilt from scratch in many transforms. - P1 — Extend
addImportwith sorted/last-import insertion.react-proptypes-to-prop-types(firstSortedImportPosition/firstSortedRequirePosition),replace-reactdom-render(currently inserts at offset 0), andremove-legacy-contextreinvent it. - P1 — Add
remove-lines(orremoveStatement) returningEdit[].lineStart/consumeLineBreak/consumeBlankLine/removalEditare duplicated almost verbatim across many transforms. - P2 — Promote
resolve-binding-path. Several transforms do bespokemember_expressionwalks to detectReact.foovs importedfoo. - P2 — Add
get-scopewith multi-kind stop list (e.g.[statement_block,class_body,program]). - P2 — Promote
indent(detectIndentUnit,getLineIndent).
Sub-tasks (per util)
Each box below should become its own follow-up issue/PR. Migrating a transform usually means deleting the local helpers, importing the equivalent from
@jssg/utils, and re-running the snapshot tests.
P0 — import-statement / require-call / module-dependencies
- Upstream the helpers (or a unified
getModuleDependenciesAPI) to@jssg/utils. - Migrate
codemods/create-element-to-jsx(hasReact,requireSource,importSource). - Migrate
codemods/find-dom-node(hasReact,requireSource,importSource). - Migrate
codemods/pure-render-mixin(hasReact). - Migrate
codemods/sort-comp(hasReact,reactImportAliases). - Migrate
codemods/pure-component(reactImportAliasesover multiple sources). - Migrate
codemods/react-to-react-dom(findModuleImports,findRequireBinding,findAssignmentBinding). - Migrate
codemods/react-native-view-prop-types(the sixfind*finders). - Migrate
codemods/replace-react-test-renderer-import(replacementPatternForLiteral). - Migrate
codemods/update-react-imports(isReactImport,getReactSpecifier,namedSpecifiers). - Migrate
codemods/remove-memoization(collectReactImports,buildReactObjectNames,buildNamedImportMap). - Migrate
codemods/replace-create-factory(collectReactImports). - Migrate
codemods/remove-legacy-context(collectReactImportInfo). - Migrate
codemods/replace-act-import(test-utils import lookup). - Migrate
codemods/replace-use-form-state(findReactDOMMemberImportNames,findNamedUseFormStateImports). - Migrate
codemods/replace-reactdom-render(findReactDomMemberImportNames,findNamedImportNames). - Migrate
codemods/use-context-hook(findReactMemberImportNames,findNamedUseContextImports). - Migrate
codemods/react-proptypes-to-prop-types(propTypesBindingFromModule). - Migrate
codemods/prop-types-typescript(collectPropTypesImports).
P0 — update-binding / remove-binding
- Upstream to
@jssg/utils(rename / merge / append / fall-back-to-side-effect-import). - Migrate
codemods/react-dom-to-react-dom-factories(renameDOM→createElement). - Migrate
codemods/use-context-hook(renameuseContext→use, preserving alias). - Migrate
codemods/update-react-imports(default/namespace → named conversion, dedupe). - Migrate
codemods/replace-use-form-state(dropuseFormStatefromreact-dom, add toreact). - Migrate
codemods/replace-act-import(removeNodeWithComma+ specifier merge). - Migrate
codemods/pure-component(buildImportSpecifierEdits). - Migrate
codemods/replace-create-factory(importStatementReplacement). - Migrate
codemods/remove-memoization(importStatementReplacement). - Migrate
codemods/react-to-react-dom(splitreact→react/react-dom/react-dom/server). - Migrate
codemods/react-proptypes-to-prop-types(dropPropTypesfrom React). - Migrate
codemods/react-native-view-prop-types(appendViewPropTypestoreact-native). - Migrate
codemods/prop-types-typescript(cleanupPropTypesImports). - Migrate
codemods/remove-legacy-context(specifier rewrites in React import).
P1 — addImport with sorted/last-import insertion
- Extend
addImportAPI in@jssg/utilsto support sorted-by-source and last-import-anchor placement. - Migrate
codemods/replace-reactdom-render(createRootinsertion currently at offset 0). - Migrate
codemods/replace-act-import(mergeactinto existing react named import or insert new). - Migrate
codemods/replace-use-form-state(useActionStateinsertion). - Migrate
codemods/react-proptypes-to-prop-types(firstSortedImportPosition/firstSortedRequirePosition). - Migrate
codemods/react-to-react-dom(buildImportLine/buildRequireLine). - Migrate
codemods/remove-legacy-context(the appendedimport * as React from "react";). - Migrate
codemods/react-native-view-prop-types(ViewPropTypesinsertion). - Migrate
codemods/update-react-imports(rebuild named clause).
P1 — remove-lines / removeStatement
- Upstream an
Edit-returning variant to@jssg/utils. - Migrate
codemods/manual-bind-to-arrow(statementRemovalEdit,nodeRemovalEdit). - Migrate
codemods/replace-default-props(removeStatementEdit). - Migrate
codemods/remove-legacy-context(removalEdit, line-helpers). - Migrate
codemods/replace-create-factory(removeImportStatementEdit). - Migrate
codemods/remove-memoization(removeImportStatementEdit). - Migrate
codemods/pure-component(lineDeletionRange). - Migrate
codemods/pure-render-mixin(declarationRemovalRange). - Migrate
codemods/replace-act-import(line-aware specifier removal). - Migrate
codemods/update-react-imports(statementRange-based removal). - Migrate
codemods/react-proptypes-to-prop-types(statementRange,statementRangeWithLeadingLineComments). - Migrate
codemods/prop-types-typescript(cleanupPropTypesImportsregex pass).
P2 — resolve-binding-path
- Upstream to
@jssg/utils. - Migrate
codemods/find-dom-node(getDOMNoderesolution). - Migrate
codemods/react-dom-to-react-dom-factories(React.DOM.foo→React.createElement(...)). - Migrate
codemods/replace-reactdom-render(ReactDOM.rendervs importedrender). - Migrate
codemods/replace-act-import(getTestUtilsImport). - Migrate
codemods/replace-use-form-state(ReactDOM.useFormStateresolution). - Migrate
codemods/react-to-react-dom(React.findDOMNode/React.renderToStringclassification). - Migrate
codemods/replace-create-factory(createFactoryCallInfo). - Migrate
codemods/remove-memoization(memoizationCallInfo). - Migrate
codemods/update-react-imports(React.foo→ barefoo). - Migrate
codemods/use-context-hook(React.useContextvs nameduseContext).
P2 — get-scope (multi-kind stop list)
- Upstream to
@jssg/utils. - Migrate
codemods/manual-bind-to-arrow(getClassBody, ancestor walks). - Migrate
codemods/replace-string-ref(isInsideReactClassComponent). - Migrate
codemods/replace-default-props(enclosingStatement,topLevelStatement). - Migrate
codemods/react-proptypes-to-prop-types(nearestStatement). - Migrate
codemods/prop-types-typescript(topLevelStatement). - Migrate
codemods/pure-component/codemods/pure-render-mixin(recurring ancestor walks). - Migrate
codemods/replace-act-import(ancestor walks). - Migrate
codemods/replace-create-factory/codemods/remove-memoization(isInsideImport,selectedAncestorForIndex).
P2 — indent (detectIndentUnit, getLineIndent)
- Upstream to
@jssg/utils. - Migrate
codemods/replace-reactdom-render(reindentText,getIndent). - Migrate
codemods/replace-default-props(bodyStatementIndent). - Migrate
codemods/create-element-to-jsx(lineIndent). - Migrate
codemods/manual-bind-to-arrow(leading-whitespace probing). - Migrate
codemods/prop-types-typescript(indent detection for inserted interfaces).
Out of scope
These transforms don't materially benefit from any of the listed utils:
codemods/error-boundaries— pure declarativeproperty_identifierrename.codemods/rename-unsafe-lifecycles— pure declarativeproperty_identifierrename.codemods/remove-context-provider— pure JSX-name rewrite, no imports/scope.codemods/react-19-migration-recipe— workflow recipe, no transform script.
Notes / open questions
- Should multi-source detection live as an extension of
getImport(e.g.from: string[], returnsBinding[]), or as a separategetModuleDependenciesAPI? Several transforms (react-to-react-dom,pure-component,sort-comp, etc.) need both ESM imports and CJSrequirecalls and latevar X; X = require(...)patterns in one pass. update-bindingneeds to support: rename, merge with existing specifier, append new specifier, drop named clause while preserving default, and fall back to bare side-effectimport 'src';when the last specifier is removed (legacyImportSpecifier.remove()behavior). Make sure the API surface is rich enough.addImportneeds an insertion-point strategy parameter (e.g.placement: "sorted" | "after-last-import" | "top"), and ideally should de-dupe against an existing matching import.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.