Goal: Determine whether the new JSSG (ast-grep) codemods regress any intentional behavior compared to the relevant baseline: legacy jscodeshift codemods from
reactjs/react-codemod, orcodemod/commonswhere no legacy transform exists.
| Item | Detail |
|---|---|
| JSSG codemods | react-* packages plus current local branch implementations under codemods/ for newly ported or not-yet-published work such as react-prop-types-typescript |
| Comparison baselines | react/19/* (jscodeshift, from Codemod Registry) for published transforms; official reactjs/react-codemod checkout at 5207d594fad6f8b39c51fd7edd2bcb51047dc872 for unpublished legacy transforms; prop-types-typescript compared against codemod/commons/codemods/react/prop-types-typescript |
| CLI | codemod@latest with --no-interactive flag |
| Test repos | youzan/zent (React 17, TS), salesforce/design-system-react (React 17, JS), MetaMask/metamask-extension (React 16/17-era app), azat-co/react-quickly (React ~15, JS/JSX), atlassian/react-beautiful-dnd (React 16.13, JS+Flow), calcom/cal.diy (redirect from calcom/cal.com as of 2026-04-17, React 18/19 monorepo, tested at v6.2.0 / 1c193cc), plus targeted imported-codemod slices from DataTurks and react-native-snap-carousel |
| Phase 2 import verification | On 2026-04-20, imported 14 additional JSSG codemods from align-with-legacy-codemods and verified them with pnpm run test, pnpm run check-types, and pnpm run ci on this branch |
Reading this table: each codemod was exercised against more than one repo slice. The
JSSG Files / Baseline Filescolumns below show the headline repo for that codemod (identified in theSource repocolumn), not a union across all slices. Unless noted otherwise, the baseline is the legacy jscodeshift transform fromreactjs/react-codemod. Per-repo breakdowns live in the detailed sections.
| Codemod | Verdict | Source repo | JSSG Files | Baseline Files | Notes |
|---|---|---|---|---|---|
replace-reactdom-render |
Safe but conservative | youzan/zent |
4 | 4 | youzan/zent remains clean; on salesforce/design-system-react, JSSG transforms 1 file and legacy transforms 6 because JSSG now skips unsafe helper patterns that rely on the return value of ReactDOM.render(...) (see §1) |
replace-act-import |
JSSG wins | MetaMask/metamask-extension |
18 | 18 | Shown repo is a parity check; the "JSSG wins" verdict comes from react-beautiful-dnd where JSSG transforms 6 files vs legacy 1 (see §2) |
use-context-hook |
JSSG wins | calcom/cal.diy (v6.2.0) |
30 | 29 | youzan/zent was byte-identical 47/47 (see §3); cal.com adds 2 real call sites and avoids 1 unused-import false positive; salesforce/design-system-react adds a 6-file JS spot-check |
replace-string-ref |
JSSG wins | azat-co/react-quickly |
5 | 0 | Legacy skips .jsx files entirely |
replace-use-form-state |
Perfect parity | synthetic fixture | 1 | 1 | Fixed: now moves import from react-dom to react |
prop-types-typescript |
JSSG wins | MetaMask/metamask-extension |
275 | 263 | Compared against commons, not legacy. JSSG now has zero sampled parse failures, never misses a commons file, and covers 12 additional MetaMask files that commons crashes on (see §6) |
react-proptypes-to-prop-types |
JSSG wins | nylas/nylas-mail |
135 | 109 | Official legacy transform is not on the registry, but local jscodeshift evaluation shows JSSG handles 26 additional real files that the upstream transform errors on |
Bottom line: the previously tested codemods remain in good shape, and the commons-aligned prop-types-typescript step is now in better shape than the commons baseline on the sampled repos. JSSG has zero sampled parse failures, never misses a file that commons changes, and the remaining output differences are either safe JSSG improvements or non-functional printer drift.
Earlier iterations of this report compared fixture-corpus runtimes between the JSSG and legacy packages through codemod@latest. Those numbers were dominated by CLI/pnpm dlx startup and, at the time of measurement, were also distorted by a transient performance regression in the CLI that has since been fixed. They were removed to avoid misleading conclusions.
The authoritative speed comparison is now a production-scale real-repo run of the React 19 migration recipe against calcom/cal.com (~7.5k input files), with both sides executed through codemod@latest:
| Codemod | Corpus | Input Files | Changed Files (J/L) | JSSG | Legacy CLI | Faster | Notes |
|---|---|---|---|---|---|---|---|
react-19-migration-recipe |
calcom/cal.com repo |
7500 | 4 / 4 | ~18s | ~3m00s | JSSG (~10×) | Ran on a real repo, JSSG matched results in 10x less time |
At production scale, JSSG runs ~10x faster than the legacy recipe on real codebases.
| Codemod | Evaluation Type | Result | Notes |
|---|---|---|---|
create-element-to-jsx |
34 fixtures + error/differential tests | Green | Strongest imported parity signal in this pass |
error-boundaries |
2 fixtures | Green | Import landed cleanly; real-repo sampling still pending |
find-dom-node |
9 fixtures | Green | Fixture suite and type checks are green |
manual-bind-to-arrow |
12 fixtures | Green | Fixture suite, regression fixture, and type checks are green |
pure-component |
11 fixtures + warning/differential tests | Green | Includes checked-in parity fixtures and warning behavior checks |
pure-render-mixin |
7 fixtures | Green | Fixture suite and type checks are green |
react-dom-to-react-dom-factories |
11 fixtures | Green | Fixture suite, nested-call regression fixture, and type checks are green |
react-native-view-prop-types |
12 fixtures | Green | Fixture suite and type checks are green |
react-to-react-dom |
14 fixtures + error tests | Green | Includes explicit error-path coverage |
remove-context-provider |
7 fixtures | Green | Fixture suite and type checks are green |
remove-forward-ref |
18 fixtures | Green | Fixture suite, generic-signature regression fixture, and type checks are green |
rename-unsafe-lifecycles |
9 fixtures | Green | Fixture suite and type checks are green |
sort-comp |
11 fixtures | Green | Fixture suite and type checks are green |
update-react-imports |
33 fixtures | Green | Large imported fixture surface is green |
A follow-up repo-based investigation on 2026-04-20 re-ran the sampled imported codemods and used normalized AST comparison (including JSX literal whitespace normalization) to separate printer drift from semantic drift. That pass confirmed that the suspected formatting-only disparities were indeed non-semantic, and it exposed three true JSSG gaps. All three were patched on this branch and re-evaluated against the same repo slices.
After the import, I checked whether the imported codemods have real use cases in the repos already referenced by the testing plan. They do. The first side-by-side sampling pass below used targeted source-only slices to avoid generated bundles and third-party vendored code.
| Codemod | Repo Slice | Verdict | JSSG Files | Legacy Files | Notes |
|---|---|---|---|---|---|
create-element-to-jsx |
react-quickly source chapters (ch03/ch05/ch09/ch10/ch11/ch17) |
Semantic parity | 1 | 1 | Both only transformed ch17/node/email.js; normalized ASTs match after JSX literal whitespace normalization |
manual-bind-to-arrow |
react-quickly source files with constructor binds |
Semantic parity (fixed) | 13 | 13 | Fixed anonymous class-expression support and constructor-line cleanup; all 13 transformed files now normalize equal |
find-dom-node |
react-quickly spare-parts source hits |
No actionable source hit | 0 | 0 | Filtered source hits were skipped by both transforms |
error-boundaries |
DataTurks error boundary component |
Semantic parity | 1 | 1 | Exact unstable_handleError rename in production source; outputs normalize equal |
react-dom-to-react-dom-factories |
react-quickly jQuery Mobile example app |
Semantic parity (fixed) | 1 | 1 | Fixed overlapping nested factory edits; the transformed file now normalizes equal end-to-end |
rename-unsafe-lifecycles |
nylas-mail app source + internal packages |
Semantic parity | 45 | 45 | Same file set transformed; all 45 transformed files normalize equal |
remove-forward-ref |
calcom/cal.diy matched source files |
JSSG ahead | 4 | 2 | Fixed dropped generic signature preservation; the 2 overlapping files now normalize equal and JSSG still handles 2 real generic/member-expression cases that legacy skips |
remove-context-provider |
calcom/cal.diy matched source files |
Semantic parity | 22 | 22 | Same file set transformed; all 22 transformed files normalize equal |
react-native-view-prop-types |
react-native-snap-carousel source files |
JSSG safer | 4 | 4 | JSSG now preserves valid existing ViewPropTypes imports; legacy still emits duplicate imports and becomes syntactically invalid in all 4 overlapping files |
update-react-imports |
youzan/zent TS/TSX source slice |
Inconclusive on coverage; no semantic drift | 4 | 1 | Legacy still hits a parser error on the TS-heavy slice; the overlapping file normalizes equal and the 3 JSSG-only rewrites are whitespace-only no-ops |
Key conclusion from the imported-codemod disparity investigation: after the 2026-04-20 fixes and the 2026-04-21 react-native-view-prop-types import-duplication fix, the sampled imported codemods no longer have any confirmed JSSG semantic regressions versus legacy. The remaining non-identical outputs are either printer drift with normalized-AST parity (create-element-to-jsx, rename-unsafe-lifecycles, remove-context-provider, manual-bind-to-arrow, react-dom-to-react-dom-factories) or intentional JSSG-only coverage differences (remove-forward-ref). update-react-imports still needs a cleaner comparison target if we want a fair file-coverage verdict beyond the legacy parser failure.
Repo: youzan/zent (packages/zent/src/)
Both codemods now produce equivalent output across all 4 files:
| File | JSSG | Legacy |
|---|---|---|
dialog/open.tsx |
✅ | ✅ |
preview-image/previewImage.tsx |
✅ | ✅ |
notify/Notify.tsx |
✅ | ✅ |
notice/Container.tsx |
✅ | ✅ |
What was fixed:
- Added
ReactDOM.unmountComponentAtNode(container)→createRoot(container).unmount()pattern (member + named import) - Fixed indentation bug caused by UTF-8 byte offset vs JS string index mismatch in files with non-ASCII characters (e.g. Chinese comments)
- Added multi-line JSX formatting: elements spanning multiple lines are placed on a new line inside
render()with correct re-indentation
Example transformation (Notify.tsx):
- ReactDOM.unmountComponentAtNode(container);
+ const root2 = createRoot(container);
+ root2.unmount();- ReactDOM.render(
- <NotifyContent
- isIn={false}
- text={text}
- />,
- container
- );
+ const root1 = createRoot(container);
+ root1.render(
+ <NotifyContent
+ isIn={false}
+ text={text}
+ />
+ );This second repo changed the verdict.
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 1 | 6 |
| Overlap files | 1 | 1 |
| Overlap semantic parity | ✅ | — |
The salesforce repro exposed the real root cause: some helper files rely on the return value of ReactDOM.render(...), typically via return ReactDOM.render(...) in test setup helpers. Rewriting those to createRoot(...).render(...) is not semantics-preserving because root.render(...) does not return the rendered instance. The legacy codemod rewrites those files anyway; that is not safe.
Current local JSSG behavior is now intentionally conservative:
- direct statement-form
ReactDOM.render(...)calls still transform - files that use the
render(...)return value are skipped entirely
On salesforce/design-system-react, that means JSSG currently skips the five unsafe helper files below instead of performing an invalid rewrite:
components/input/__tests__/input.browser-test.jsxcomponents/menu-dropdown/__tests__/dropdown.browser-test.jsxcomponents/menu-picklist/__tests__/picklist-base.browser-test.jsxcomponents/slider/__tests__/slider.browser-test.jsxcomponents/textarea/__tests__/textarea.browser-test.jsx
The one remaining overlap file, components/modal/trigger.jsx, differs only by formatting/comment placement in the current comparison.
Treat the current divergence as an intentional safety gap rather than a remaining regression. youzan/zent remains clean, and the salesforce repro is now handled by skipping unsafe return-value patterns instead of corrupting files.
Repo: atlassian/react-beautiful-dnd (test/)
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 6 | 1 |
Both codemods produce the same correct transformation per file:
-import { act } from 'react-dom/test-utils';
+import { act } from "react";But the legacy codemod only picks up 1 of 6 files (touch-sensor/click-blocking.spec.js), while JSSG correctly finds and transforms all 6. The legacy codemod appears to have a file-matching or traversal limitation that causes it to miss files in nested test directories.
As of April 17, 2026, GitHub redirects calcom/cal.com to calcom/cal.diy. On tag v6.2.0, both the local JSSG workflow and the legacy registry codemod transform the same single file:
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 1 | 1 |
| Diff comparison | Byte-identical | — |
File transformed: packages/ui/components/form/color-picker/colorpicker.test.tsx
-import { act } from "react-dom/test-utils";
+import { act } from "react";On current MetaMask, both codemods transform the same 18 files under ui/, and the overlapping outputs are semantically equal. This is a useful complement to react-beautiful-dnd because it exercises a modern TS-heavy test tree without changing the verdict: JSSG is still safe here, and the previous react-beautiful-dnd coverage win still stands.
No action needed — JSSG is strictly better here.
Repo: youzan/zent (packages/zent/src/)
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 47 | 47 |
| Insertions | 104 | 104 |
| Deletions | 104 | 104 |
| Diff comparison | Byte-identical | — |
Running diff on the two saved diffs produced empty output. Every file, every line, every character is identical between the two codemods. This is the gold standard result.
Transformation pattern (applied consistently across all 47 files):
-import { useContext } from 'react';
+import { use } from 'react';
...
-const value = useContext(SomeContext);
+const value = use(SomeContext);For this add-on validation, JSSG was run from the current local workflow in this repo and legacy was run from the published react/19/use-context-hook package.
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 30 | 29 |
| Insertions | 58 | 55 |
| Deletions | 58 | 55 |
| Common-file diff comparison | 28 files, byte-identical | — |
The overlapping 28 file diffs are byte-identical. The difference comes from three file-targeting decisions:
- JSSG-only:
apps/web/modules/users/components/UserTable/BulkActions/MassAssignAttributes.tsx - JSSG-only:
packages/features/embed/lib/hooks/useEmbedDialogCtx.tsx - Legacy-only:
apps/web/modules/notifications/components/WebPushContext.tsx
The two JSSG-only files contain real useContext(...) call sites:
- const context = useContext(AttributesContext);
+ const context = use(AttributesContext);- const context = useContext(EmbedDialogContext);
+ const context = use(EmbedDialogContext);The legacy-only file does not contain a useContext(...) call; it only had an unused useContext import, which legacy rewrote to use anyway:
-import { createContext, useContext, useEffect, useMemo, useState } from "react";
+import { createContext, use, useEffect, useMemo, useState } from "react";On the 6 real useContext(...) files under components/, both codemods transform the same file set. Four outputs normalize equal directly. The remaining two inspected diffs in components/data-table/cell.jsx and components/data-table/private/row.jsx are wrapper-parenthesis / indentation drift only; I did not find a semantic behavior change there.
No action needed — zent remains byte-identical parity, cal.com shows a safe modern-repo improvement, and salesforce adds a small JS-side parity spot-check.
Repo: azat-co/react-quickly (full repo)
| Metric | JSSG | Legacy |
|---|---|---|
| Files transformed | 5 | 0 |
| String refs replaced | 9 | 0 |
The legacy codemod reports "No changes were made during the codemod run" despite the repo containing numerous string refs in .jsx files. The legacy jscodeshift transform appears to have a file extension filtering issue — all target files are .jsx, and the legacy codemod seems to skip non-.js/.ts files.
JSSG correctly transforms all instances:
-<input ref="emailAddress" type="text" />
+<input ref={(ref) => { this.refs.emailAddress = ref; }} type="text" />Files transformed by JSSG: ch07/email/jsx/content.jsx, ch11/homework/jsx/content.jsx, ch12/email/email-webpack/jsx/content.jsx (×2 refs), ch12/email/email-webpack/source/content.jsx (×2 refs), ch17/message-board/source/app.jsx.
No action needed — JSSG is strictly better here.
Repo: synthetic fixture (no real-world repos use useFormState from react-dom)
Input:
import { useFormState } from "react-dom";
function Form() {
const [state, formAction] = useFormState(action, initialState);
return <form action={formAction}>{state}</form>;
}| Aspect | JSSG Output | Legacy Output |
|---|---|---|
| Rename | useFormState → useActionState ✅ |
useFormState → useActionState ✅ |
| Import source | import { useActionState } from "react" ✅ |
import { useActionState } from "react" ✅ |
What was fixed:
- Named imports: now rewrites the import source from
"react-dom"to"react"(or splits when other specifiers remain) - Member access (
ReactDOM.useFormState): now replaces the entire expression withuseActionStateand adds areactimport - Handles quote style preservation, aliases, default imports + named splits, and multiple react-dom import statements
Example (mixed imports):
-import { createPortal, useFormState } from "react-dom";
-import * as ReactDOM from "react-dom";
+import { createPortal } from "react-dom";
+import { useActionState } from "react";
+import * as ReactDOM from "react-dom";
...
- const [state, formAction] = useFormState(increment, 0);
+ const [state, formAction] = useActionState(increment, 0);
- const otherState = ReactDOM.useFormState(increment, 0);
+ const otherState = useActionState(increment, 0);No action needed — regressions resolved.
This codemod does not have a reactjs/react-codemod equivalent. The comparison baseline here is the newer codemod/commons implementation at codemods/react/prop-types-typescript.
Across the three relevant repo slices below there were zero commons-only files: JSSG did not miss a single file that commons transformed. After the latest parity fixes, JSSG also has zero sampled parse failures.
| Repo slice | Matched files | JSSG | commons |
Overlap | Notes |
|---|---|---|---|---|---|
salesforce/design-system-react |
98 | 97 | 97 | 97 | Same file coverage; commons crashes on 1 file and JSSG parses all overlapping outputs |
MetaMask/metamask-extension |
275 | 275 | 263 | 263 | JSSG covers 12 additional files because commons crashes on them; JSSG parses all overlapping outputs |
atlassian/react-beautiful-dnd |
2 | 2 | 2 | 2 | Clean parity spot-check |
This is the clean control case. Both codemods transform the same 2 files, and both transformed files normalize equal. On the small real-world surface that repo exposes, JSSG is already at parity with commons.
On design-system-react, both codemods transform the same 97 files from a 98-file inline-propTypes surface. commons crashes on components/lookup/lookup.jsx with:
TypeError: Cannot read properties of null (reading 'name')
After JSX-whitespace normalization, 95 of the 96 parseable overlap files are AST-equal. The only remaining non-equal file is components/utilities/menu-list/index.jsx, and the delta there is non-functional indentation drift inside a CSS template literal. JSSG also parses components/popover/popover.jsx successfully, while commons still emits an invalid function-type union there.
MetaMask is the stress case. JSSG transforms all 275 matched files. commons transforms 263 and crashes on 12 real files, with representative failures like:
ui/components/app/permissions-connect-header/permissions-connect-header.component.js
Error: undefined does not match field "key": Expression of type TSPropertySignature
ui/components/multichain/account-list-item-menu/account-list-item-menu.js
Error: undefined does not match field "name": string of type Identifier
After the latest fixes:
- JSSG covers 12 additional real files because
commonscrashes on them. - JSSG parses all 275 transformed outputs.
- In the 263-file overlap, 244 parseable outputs are AST-equal after JSX-whitespace normalization.
The 9 remaining AST differences are all safe JSSG divergences:
configure-snap-popup.tsx,srp-details-modal.tsx,account-network-indicator.tsx,domain-input-resolution-cell.tsx,network-list-item.tsx,page/page.tsx, andqr-code-view.tsx: JSSG preserves the file’s existing inline or named parameter types instead of replacing them with a weaker generated*Propsinterface frompropTypes.permission-cell.js: JSSG converts aoneOfType([string, object])shape tostring | object, whilecommonswidens the same field tounknown | unknown.modal.js: the remaining delta is non-functional output drift around the generated interface location and a cleaned-up leading directive/empty-statement sequence.
Two especially important bugs were closed by this pass:
- imported/bare
PureComponentandComponentclasses now receive props generics correctly - removing
static propTypesor nestedComponent.propTypes = ...no longer leaves stray tokens or deletes enclosing helper functions/test blocks
Treat prop-types-typescript as stronger than the sampled commons baseline now. The remaining differences are safe enough to keep unless you want to spend more time chasing output-shape parity for already-typed files.
This legacy carryover is still useful to track separately, but it is no longer the React 19 recipe step after aligning the recipe with codemod/commons.
The legacy counterpart (React-PropTypes-to-prop-types) is not published on the Codemod Registry, but it does exist in the upstream reactjs/react-codemod repo. For this comparison I ran the official transform directly with local jscodeshift from a checkout at commit 5207d594fad6f8b39c51fd7edd2bcb51047dc872.
To avoid vendored React bundles, I compared an authored slice containing the real source hits:
ch13/router/jsx/content.jsxspare-parts/ch08-es5/prop-types/script.jsxspare-parts/ch11-old/router/react-router-active-component.jsspare-parts/tooltip-logger (mixin)/jsx/button.jsxspare-parts/tooltip-logger (mixin)/js/button.js
| Metric | JSSG | Official legacy |
|---|---|---|
| Files transformed | 5 | 2 |
| Legacy errors | — | 3 |
| Common-file semantic parity | 2/2 | — |
The overlapping 2 files are semantically equivalent after normalization.
The 3 JSSG-only files are real authored source files that the official legacy transform errors on with No PropTypes import found!. The reason is clear from the inputs: those files use React.PropTypes with global React, so the upstream codemod cannot infer where to insert the prop-types import/require. JSSG handles them cleanly.
Representative JSSG-only example:
+var PropTypes = require('prop-types');
...
- handler: React.PropTypes.func.isRequired,
- title: React.PropTypes.string,
+ handler: PropTypes.func.isRequired,
+ title: PropTypes.string,For a larger production slice, I compared all .jsx files under:
packages/client-app/srcpackages/client-sync/srcpackages/client-app/internal_packagespackages/client-app/static
that still reference React.PropTypes.
| Metric | JSSG | Official legacy |
|---|---|---|
| Files transformed | 135 | 109 |
| Legacy errors | — | 26 |
| Common-file overlap | 109 | 109 |
| Common-file semantic parity | 109/109 after inspection | — |
The official legacy transform fails on 26 real files, mostly with No PropTypes import found!. A representative example is packages/client-app/src/components/code-snippet.jsx, which imports React from nylas-exports instead of from react directly:
import {React} from 'nylas-exports';
...
CodeSnippet.propTypes = {
intro: React.PropTypes.string,
}JSSG handles this case and emits valid output:
+import PropTypes from 'prop-types';
...
- intro: React.PropTypes.string,
+ intro: PropTypes.string,The only non-identical overlap files I found on manual inspection were:
packages/client-app/src/components/nylas-calendar/calendar-toggles.jsxpackages/client-app/src/components/nylas-calendar/week-view.jsx
Those differences are import/comment formatting drift only; I did not find a transformation-logic difference there.
After the repo comparison, I validated every JSSG-only transformed file individually.
react-quickly: all 3 JSSG-only files contain realReact.PropTypesmember expressions in authored source, parse cleanly after transformation, and correctly receive a CommonJSprop-typesbinding (var/const require(...)) matching the file style.nylas-mail: all 26 JSSG-only files contain realReact.PropTypesmember expressions and now parse cleanly after transformation.
This pass did uncover one real JSSG issue on current-branch output: when inserting a new import PropTypes from 'prop-types'; after the last existing import, the codemod could glue the new import onto the previous import line in some files (for example packages/client-app/src/components/dropdown-menu.jsx). I fixed that insertion offset and added a regression fixture for the nylas-exports import shape. After rerunning the repo slice, all 26 JSSG-only nylas-mail files parse successfully and retain zero React.PropTypes references.
Treat react-proptypes-to-prop-types as directly compared now. The official upstream jscodeshift transform is more brittle on real repos than JSSG, and JSSG is the stronger implementation on both react-quickly and nylas-mail.
The following codemods were originally ported to JSSG on align-with-legacy-codemods and imported into the current branch on April 20, 2026, while preserving the six newer superseding codemods already present here:
create-element-to-jsxerror-boundariesfind-dom-nodemanual-bind-to-arrowpure-componentpure-render-mixinreact-dom-to-react-dom-factoriesreact-native-view-prop-typesreact-to-react-domremove-context-providerremove-forward-refrename-unsafe-lifecyclessort-compupdate-react-imports
Post-import verification on this branch:
pnpm run test✅pnpm run check-types✅pnpm run ci✅
Interpretation:
- The branch now carries 20 active JSSG codemods under
codemods/. - The imported 14 codemods are fixture-verified as a group; several now also have targeted real-repo evidence, but the full imported set is not yet uniformly repo-certified.
classis the only codemod still legacy-only.
Previously identified regressions were fixed and retested. The later prop-types-typescript reevaluation closed the remaining confirmed functional disparities on the sampled repo surface, so the outstanding items below are either intentional safety gaps or already-accepted output-shape differences.
| # | Codemod | Issue | Resolution |
|---|---|---|---|
| 1 | replace-reactdom-render |
Missing unmountComponentAtNode() pattern |
✅ Added member + named import matching for unmountComponentAtNode → createRoot().unmount() |
| 2 | replace-reactdom-render |
Indentation bugs (byte offset vs char index with non-ASCII) | ✅ Rewrote getIndent() to use line-based approach; added reindentText() for multi-line JSX |
| 3 | replace-use-form-state |
Import source not changed from react-dom to react |
✅ Rewrote import handling: direct node replacement with source splitting, quote preservation, alias support |
| 4 | manual-bind-to-arrow |
Missed anonymous class expressions assigned to module.exports, so react-quickly/ch13/naive-router/jsx/router.jsx was skipped |
✅ Expanded class lookup to cover class expressions and fixed constructor-line deletion so the remaining constructor body stays well-formed |
| 5 | remove-forward-ref |
Rebuilt function-expression wrappers dropped generic type parameters (and could also drop return-type syntax) in real code such as FormActions.tsx |
✅ Preserved the original signature prefix/suffix around rewritten params and added a generic-signature regression fixture |
| 6 | react-dom-to-react-dom-factories |
Nested React.DOM.* replacements were lost because the transform emitted overlapping outer/inner edits |
✅ Rewrote only top-level matches and recursively transformed nested factory calls inside their argument subtrees; added a nested-call regression fixture |
| 7 | react-native-view-prop-types |
Duplicate ViewPropTypes imports when the file already imported ViewPropTypes, producing invalid output on react-native-snap-carousel |
✅ Reused the existing ViewPropTypes binding instead of inserting another import/specifier; added a real-world regression fixture |
| 8 | replace-reactdom-render |
Real-world helper files used return ReactDOM.render(...); rewriting them changed semantics and, earlier in investigation, could corrupt surrounding code |
✅ Added safety guards and regression fixtures so the codemod now skips return-value-dependent render patterns instead of rewriting them unsafely |
| 9 | react-proptypes-to-prop-types |
In some import-style files, the inserted prop-types import could be glued onto the previous import line, producing invalid syntax |
✅ Insert after the full previous import statement (including its line break) and added a nylas-exports regression fixture |
| Codemod | Status |
|---|---|
replace-reactdom-render |
No remaining confirmed functional regression, but an intentional coverage gap remains: return-value-dependent helper patterns are skipped for safety instead of being rewritten like legacy |
replace-act-import |
No action needed — JSSG still wins overall, and MetaMask adds an 18-file semantic-parity check |
use-context-hook |
No action needed — zent is byte-identical, cal.com shows a safe extension, and salesforce adds a 6-file JS-side parity spot-check |
replace-string-ref |
No action needed — JSSG outperforms legacy (handles .jsx files) |
prop-types-typescript |
No remaining confirmed functional disparity on the sampled repos — JSSG is ahead on coverage (0 commons-only files, 12 MetaMask JSSG-only files because commons crashes), has zero sampled parse failures, and the remaining output deltas are safe JSSG improvements or non-functional drift |
react-proptypes-to-prop-types |
No action needed — official upstream jscodeshift comparison now exists, and JSSG is stronger on both sampled repo slices |
| Imported 14 codemods | Branch integration is green. The sampled imported codemods now have stronger repo-based evidence: error-boundaries has exact-source parity on DataTurks, react-native-view-prop-types is safer than legacy on react-native-snap-carousel, and update-react-imports still needs a cleaner comparison target beyond the current legacy parser failure |
class |
Still legacy-only — no JSSG port exists on this branch yet |