Skip to content

Tracking: adopt shared @jssg/utils helpers across react-codemod transforms #5

@alexbit-codemod

Description

@alexbit-codemod

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:

  • getImport
  • addImport
  • removeImport

Currently internal in userland-migrations/utils/src/ast-grep (candidates to upstream):

  • get-scope
  • indent (detectIndentUnit, getLineIndent)
  • remove-lines
  • update-binding
  • remove-binding
  • resolve-binding-path
  • import-statement
  • require-call
  • module-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

  1. P0 — Upstream import-statement + require-call + module-dependencies to @jssg/utils (or extend getImport to accept from: string[] and return all matches). Removes the copy-pasted ~40-line hasReact + importSource + requireSource triplet from at least 18 codemods. Bottleneck for react-to-react-dom, react-native-view-prop-types, update-react-imports, replace-react-test-renderer-import, and the Component/PureComponent-across-React-aliases finders.
  2. P0 — Add update-binding / remove-binding to @jssg/utils. removeImport already covers the drop the whole specifier half, but the rename / merge / append / fall-back-to-side-effect-import logic is rebuilt from scratch in many transforms.
  3. P1 — Extend addImport with sorted/last-import insertion. react-proptypes-to-prop-types (firstSortedImportPosition/firstSortedRequirePosition), replace-reactdom-render (currently inserts at offset 0), and remove-legacy-context reinvent it.
  4. P1 — Add remove-lines (or removeStatement) returning Edit[]. lineStart / consumeLineBreak / consumeBlankLine / removalEdit are duplicated almost verbatim across many transforms.
  5. P2 — Promote resolve-binding-path. Several transforms do bespoke member_expression walks to detect React.foo vs imported foo.
  6. P2 — Add get-scope with multi-kind stop list (e.g. [statement_block, class_body, program]).
  7. 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 getModuleDependencies API) 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 (reactImportAliases over multiple sources).
  • Migrate codemods/react-to-react-dom (findModuleImports, findRequireBinding, findAssignmentBinding).
  • Migrate codemods/react-native-view-prop-types (the six find* 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 (rename DOMcreateElement).
  • Migrate codemods/use-context-hook (rename useContextuse, preserving alias).
  • Migrate codemods/update-react-imports (default/namespace → named conversion, dedupe).
  • Migrate codemods/replace-use-form-state (drop useFormState from react-dom, add to react).
  • 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 (split reactreact/react-dom/react-dom/server).
  • Migrate codemods/react-proptypes-to-prop-types (drop PropTypes from React).
  • Migrate codemods/react-native-view-prop-types (append ViewPropTypes to react-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 addImport API in @jssg/utils to support sorted-by-source and last-import-anchor placement.
  • Migrate codemods/replace-reactdom-render (createRoot insertion currently at offset 0).
  • Migrate codemods/replace-act-import (merge act into existing react named import or insert new).
  • Migrate codemods/replace-use-form-state (useActionState insertion).
  • Migrate codemods/react-proptypes-to-prop-types (firstSortedImportPosition/firstSortedRequirePosition).
  • Migrate codemods/react-to-react-dom (buildImportLine / buildRequireLine).
  • Migrate codemods/remove-legacy-context (the appended import * as React from "react";).
  • Migrate codemods/react-native-view-prop-types (ViewPropTypes insertion).
  • 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 (cleanupPropTypesImports regex pass).

P2 — resolve-binding-path

  • Upstream to @jssg/utils.
  • Migrate codemods/find-dom-node (getDOMNode resolution).
  • Migrate codemods/react-dom-to-react-dom-factories (React.DOM.fooReact.createElement(...)).
  • Migrate codemods/replace-reactdom-render (ReactDOM.render vs imported render).
  • Migrate codemods/replace-act-import (getTestUtilsImport).
  • Migrate codemods/replace-use-form-state (ReactDOM.useFormState resolution).
  • Migrate codemods/react-to-react-dom (React.findDOMNode / React.renderToString classification).
  • Migrate codemods/replace-create-factory (createFactoryCallInfo).
  • Migrate codemods/remove-memoization (memoizationCallInfo).
  • Migrate codemods/update-react-imports (React.foo → bare foo).
  • Migrate codemods/use-context-hook (React.useContext vs named useContext).

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 declarative property_identifier rename.
  • codemods/rename-unsafe-lifecycles — pure declarative property_identifier rename.
  • 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[], returns Binding[]), or as a separate getModuleDependencies API? Several transforms (react-to-react-dom, pure-component, sort-comp, etc.) need both ESM imports and CJS require calls and late var X; X = require(...) patterns in one pass.
  • update-binding needs to support: rename, merge with existing specifier, append new specifier, drop named clause while preserving default, and fall back to bare side-effect import 'src'; when the last specifier is removed (legacy ImportSpecifier.remove() behavior). Make sure the API surface is rich enough.
  • addImport needs an insertion-point strategy parameter (e.g. placement: "sorted" | "after-last-import" | "top"), and ideally should de-dupe against an existing matching import.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions