fix(watcher): discover forwardRef/memo-wrapped components in AST analysis#2
Merged
Merged
Conversation
…ysis collectComponents() recognized function declarations and bare arrow/ function-expression components, but not components wrapped in React.forwardRef(...) or memo(...) — the dominant shape in classic shadcn/ui codebases. Such components were silently missed by the watcher's AST analyzer. In the VariableDeclarator visitor, unwrap a CallExpression initializer whose callee is forwardRef or memo (bare identifier or member expression like React.forwardRef), taking the first argument when it is an arrow/function expression, before the existing arrow/function- expression check. loc still spans the full VariableDeclaration and componentKey is computed as before. Adds vitest coverage for React.forwardRef, bare forwardRef, memo, and a negative case confirming unrelated call wrappers are not misdetected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the watcher’s React AST component discovery (collectComponents) so it can recognize component definitions wrapped in React.forwardRef(...) and memo(...), which are common in shadcn/ui-style codebases.
Changes:
- Extend
collectComponentsto unwrapforwardRef/memocall-expression initializers and treat the inner render function as the component shape. - Add unit tests covering
React.forwardRef(...), bareforwardRef(...),memo(...), and a negative non-component wrapper case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/watcher/src/ast/parseIntentFromReactAst.ts | Adds forwardRef/memo unwrapping in variable-declarator component discovery. |
| packages/watcher/src/ast/tests/parseIntentFromReactAst.test.ts | Adds tests validating wrapped component extraction and a negative case. |
Comment on lines
+258
to
+276
| // Unwrap React.forwardRef(...) / memo(...) wrappers so that the dominant | ||
| // shadcn/ui shape `const Button = React.forwardRef((props, ref) => ...)` | ||
| // is recognized. We look at the first argument when it is an arrow/function | ||
| // expression, then fall through to the regular check below. | ||
| let unwrapped: t.Node = init; | ||
| if (t.isCallExpression(init)) { | ||
| const callee = init.callee; | ||
| const calleeName = t.isIdentifier(callee) | ||
| ? callee.name | ||
| : t.isMemberExpression(callee) && t.isIdentifier(callee.property) | ||
| ? callee.property.name | ||
| : undefined; | ||
| if ((calleeName === 'forwardRef' || calleeName === 'memo') && init.arguments.length > 0) { | ||
| const arg = init.arguments[0]; | ||
| if (t.isArrowFunctionExpression(arg) || t.isFunctionExpression(arg)) { | ||
| unwrapped = arg; | ||
| } | ||
| } | ||
| } |
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.
What
Teaches the watcher's AST component discovery (
collectComponentsinpackages/watcher/src/ast/parseIntentFromReactAst.ts) to recognize components wrapped inReact.forwardRef(...)ormemo(...).Why
collectComponentsrecognized function declarations and bare arrow/function-expression components (const X = () => {}), but not theReact.forwardRef/memoshape:This is the dominant component shape in classic shadcn/ui codebases, so those components were silently missed by the watcher's AST analysis.
How
In the
VariableDeclaratorvisitor branch, unwrap aCallExpressioninitializer whose callee isforwardReformemo— matched as either a bare identifier (forwardRef(...)) or a member expression (React.forwardRef(...)) — taking the first argument when it is an arrow or function expression, before the existing arrow/function-expression check.locstill spans the fullVariableDeclaration(start→end lines unchanged).componentKeyis computed exactly as before.Tests
Adds a "Wrapped Component Extraction" describe block with four cases:
React.forwardRef<...>((props, ref) => ...)— asserts discovery as exported with correctcomponentName,componentKey,loc, and that inner JSX text is extracted.forwardRef((props, ref) => ...)memo(() => ...)createConfig(() => ...)) confirming unrelated call wrappers are not treated as components.All 57 tests in the file pass;
tsc --noEmitis clean.🤖 Generated with Claude Code