Skip to content

Latest commit

 

History

History
607 lines (400 loc) · 46 KB

File metadata and controls

607 lines (400 loc) · 46 KB

JSSG vs jscodeshift — Side-by-Side Testing Report

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, or codemod/commons where no legacy transform exists.

Test Environment

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

Summary

Reading this table: each codemod was exercised against more than one repo slice. The JSSG Files / Baseline Files columns below show the headline repo for that codemod (identified in the Source repo column), not a union across all slices. Unless noted otherwise, the baseline is the legacy jscodeshift transform from reactjs/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.

Speed Benchmarks

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.

Imported Codemods — Fixture Verification Summary

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.

Imported Codemods — Real Repo Sampling

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.


Detailed Findings

1. replace-reactdom-renderParity on zent, Conservative Skip on salesforce

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}
+      />
+    );

Additional rollout check: salesforce/design-system-react (825de01, React 17)

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.jsx
  • components/menu-dropdown/__tests__/dropdown.browser-test.jsx
  • components/menu-picklist/__tests__/picklist-base.browser-test.jsx
  • components/slider/__tests__/slider.browser-test.jsx
  • components/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.

Recommendation

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.


2. replace-act-importJSSG Outperforms

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.

Additional spot-check: calcom/cal.com -> calcom/cal.diy (v6.2.0, commit 1c193cc)

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";

Additional rollout check: MetaMask/metamask-extension (9c3b57c, React 17)

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.

Recommendation

No action needed — JSSG is strictly better here.


3. use-context-hookPerfect Parity on zent; JSSG Outperforms on cal.com

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);

Additional spot-check: calcom/cal.com -> calcom/cal.diy (v6.2.0, commit 1c193cc)

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";

Additional rollout check: salesforce/design-system-react (825de01, React 17)

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.

Recommendation

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.


4. replace-string-refJSSG Outperforms

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.

Recommendation

No action needed — JSSG is strictly better here.


5. replace-use-form-statePerfect Parity (Fixed)

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 useFormStateuseActionState useFormStateuseActionState
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 with useActionState and adds a react import
  • 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);

Recommendation

No action needed — regressions resolved.


6. prop-types-typescriptJSSG Outperforms commons on the Sampled Repo Surface

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

Repo slice 1: atlassian/react-beautiful-dnd

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.

Repo slice 2: salesforce/design-system-react

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.

Repo slice 3: MetaMask/metamask-extension

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 commons crashes 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, and qr-code-view.tsx: JSSG preserves the file’s existing inline or named parameter types instead of replacing them with a weaker generated *Props interface from propTypes.
  • permission-cell.js: JSSG converts a oneOfType([string, object]) shape to string | object, while commons widens the same field to unknown | 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 PureComponent and Component classes now receive props generics correctly
  • removing static propTypes or nested Component.propTypes = ... no longer leaves stray tokens or deletes enclosing helper functions/test blocks

Recommendation

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.


7. react-proptypes-to-prop-typesJSSG Outperforms Official Legacy jscodeshift

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.

Repo slice 1: azat-co/react-quickly authored source files

To avoid vendored React bundles, I compared an authored slice containing the real source hits:

  • ch13/router/jsx/content.jsx
  • spare-parts/ch08-es5/prop-types/script.jsx
  • spare-parts/ch11-old/router/react-router-active-component.js
  • spare-parts/tooltip-logger (mixin)/jsx/button.jsx
  • spare-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,

Repo slice 2: nylas/nylas-mail .jsx authored files

For a larger production slice, I compared all .jsx files under:

  • packages/client-app/src
  • packages/client-sync/src
  • packages/client-app/internal_packages
  • packages/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.jsx
  • packages/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.

Follow-up one-by-one validation of JSSG-only files

After the repo comparison, I validated every JSSG-only transformed file individually.

  • react-quickly: all 3 JSSG-only files contain real React.PropTypes member expressions in authored source, parse cleanly after transformation, and correctly receive a CommonJS prop-types binding (var/const require(...)) matching the file style.
  • nylas-mail: all 26 JSSG-only files contain real React.PropTypes member 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.

Recommendation

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.


8. Imported Codemods — Ported into This Branch and Verified

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-jsx
  • error-boundaries
  • find-dom-node
  • manual-bind-to-arrow
  • pure-component
  • pure-render-mixin
  • react-dom-to-react-dom-factories
  • react-native-view-prop-types
  • react-to-react-dom
  • remove-context-provider
  • remove-forward-ref
  • rename-unsafe-lifecycles
  • sort-comp
  • update-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.
  • class is the only codemod still legacy-only.

Action Items

Resolved Regressions

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 unmountComponentAtNodecreateRoot().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

Remaining

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