fix(codemods): wrap bare function references passed to onAction - #3075
Open
mfal wants to merge 2 commits into
Open
fix(codemods): wrap bare function references passed to onAction#3075mfal wants to merge 2 commits into
mfal wants to merge 2 commits into
Conversation
Contributor
Coverage Report for ./packages/components/
File CoverageNo changed files found. |
Contributor
🚀 Preview DeploymentPreview environments are ready:
Images:
|
mfal
force-pushed
the
fix/codemod-action-wrap-bare-reference-3060
branch
from
September 2, 2026 11:53
2ffce18 to
4de21cd
Compare
`action-prop-to-on-action` renamed the prop and left the value alone, so
`apply` asked for a manual pass over every call site. Deciding *whether* a
reference needs wrapping needs type information. Performing the wrap does not:
`() => fn()` calls what `Action` would have called, and `onAction` takes no
arguments — so the wrap fixes the reference that needed it and changes nothing
for the rest, which removes the decision.
Wrapped: a plain identifier and a member expression rooted in one, `this`
included. Left alone: an arrow function and a function expression (already the
handler), a call (`makeHandler()`, `close.bind(controller)` — it produces the
handler), and anything that is not one reference (`isOpen ? close : open`,
`onClose ?? noop`, `controller?.close`).
The wrap is applied to the surviving `onAction` whatever the prop was called
before, so a consumer who renamed by hand gets it too.
Two cases it changes and `apply` now names: a handler reading the event
`Action` forwards (undocumented, but forwarded) stops receiving it, and
`onAction={props.onAction}` becomes a call TypeScript rejects — loud, at the
call site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mfal
force-pushed
the
fix/codemod-action-wrap-bare-reference-3060
branch
from
September 2, 2026 12:50
4de21cd to
9d29d86
Compare
mfal
marked this pull request as ready for review
September 3, 2026 12:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
action-prop-to-on-actionrenamed the prop and left the value alone, so itsapplyasked for a manual pass over every call site. That instruction wasanswering the wrong question.
Deciding whether a reference needs wrapping needs type information — whether
controller.closedeclares a parameter is not in the source. Performing thewrap does not:
() => fn()calls exactly whatActionwould have called, andonActiontakes no arguments. So the wrap fixes the reference that needed itand changes nothing for the rest, which removes the decision instead of
answering it.
What the codemod does now
Wrapped — a bare reference:
action={close}→onAction={() => close()}this,action={controller.close},action={store.modal.controller.close},action={this.handleSave}Left alone:
makeHandler(),close.bind(controller),useCallback(close, []).These produce the handler, so wrapping would call the factory on every
trigger and throw its result away. This is the one shape where a naive
"wrap everything" is plainly wrong
isOpen ? close : open,onClose ?? noop,controller?.closeThe wrap targets the surviving
onActionwhatever the prop was called before,so a consumer who renamed by hand — or ran the previous version of this codemod
— gets the wrap on a second run. That also keeps it idempotent: its own output
is an arrow function, which it skips.
Where unconditional wrapping is not a no-op
Two cases, neither decidable from the source.
applyand the entry body nameboth.
Actionforwards.Actiondoesforward the trigger's event —
Button.onPress→action.execute(...args)→ActionExecutionBatch.executeBatch(...args)→ the handler. A handlerdeclaring that parameter as optional or rest type-checks both before and
after the wrap, so it silently stops receiving the event. Nothing documents
onActionas receiving an argument (ActionProps.onAction's JSDoc says only"the function executed when the action is triggered"), and no Flow-internal
handler reads one — but a consumer relying on it gets no warning. This is the
only silent case I found.
onAction={props.onAction}.undefinedis a valid value for the prop; calling it is not. The wrap turns something
TypeScript accepted into something it rejects — loudly, at the call site, so
it is a compile error to fix rather than a regression to find.
entry.md
The body's closing paragraph claimed the codemod "deliberately does not wrap",
and
applytold the reader (or agent) to check every site by hand. Bothrewritten to what the codemod now does, plus the two cases above.
MIGRATION.mdand
migrations.generated.tsregenerated from it.Verification
pnpm nx test:unit codemods— 319 pass, 13 of them this transform's fixtures(12 new: bare identifier, member expression, deep chain,
this, a valuealready spelled
onAction, arrow, function expression, call, conditional /??/ optional chain, alias + namespace import, non-FlowActionand a plain<form action>, sibling props)pnpm nx test:compile codemodscleanpnpm lintclean (0 errors; the 71 + 118 warnings are pre-existing)Overlap with #3047
#3047 (sibling issue #3061) touches the same two generated files,
packages/codemods/src/migrations.generated.tsandpackages/components/MIGRATION.md, for different entries(
renamed-css-export,table-column-width-props) and different hunks. Nosource file is shared. Whichever merges second may need
pnpm nx build codemodsre-run and the two artifacts recommitted.fixes #3060
🤖 Generated with Claude Code