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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions