Skip to content

Refactor react-docgen toward a parser-neutral core and adapters - #1156

Draft
danez wants to merge 17 commits into
mainfrom
next
Draft

danez wants to merge 17 commits into
mainfrom
next

Conversation

@danez

@danez danez commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

react-docgen currently couples extraction and extension APIs to Babel. This branch introduces a parser-neutral core and language-specific adapters so users can select a parser while retaining compatible documentation output.

This draft tracks the implementation and remaining release work described in PLAN.md. Extraction parity is established across the applicable fixture corpus; public API stabilization and release preparation remain.

Architecture and migration plan

  • @react-docgen/core owns the normalized AST, paths, traversal, scopes, shared React extraction, and parser routing, without depending on Babel, Hermes, or TypeScript.
  • @react-docgen/typescript uses TypeScript ESTree and supplies TypeScript extraction plus a preconfigured parse().
  • @react-docgen/hermes supplies JavaScript/Flow parsing and extraction plus a preconfigured parse().
  • @react-docgen/babel provides an optional compatibility adapter and a differential-testing reference.
  • Parser selection happens per file, including imported files, so projects can combine TypeScript and Flow.
  • The next react-docgen major will become a facade requiring explicit parser selection. Babel-shaped extension APIs can change in that major, with documented neutral replacements.
  • react-docgen@8 remains the maintained legacy Babel line during migration.

Documentation output is the compatibility contract. The shared portable fixture corpus defines expected behavior, and differential tests compare applicable output across adapters.

Implemented on this branch

  • Core composition APIs, parser adapters, shared React extraction, and TypeScript/Flow behavior across the applicable golden fixtures.
  • Ordered parser routing by filename, glob, regular expression, or custom selector, with per-route options and independent routing of imports.
  • A portable fixture package replacing the monolithic legacy integration snapshot, with automatic JS/TypeScript discovery and JS/Flow adapter coverage.
  • CLI configuration modules that can supply a composed parse function for single-parser or mixed-parser projects.
  • The imperative-handle useCallback and parenthesized TypeScript function fixes, including shared regression fixtures and original-author credit.

Remaining before a stable release

  • Finalize package boundaries, optional method JSDoc composition, and the standard createDocgen() convenience behavior.
  • Prove real TypeScript/Hermes cross-file imports with a dedicated mixed-parser integration fixture.
  • Reclassify the Babel-only export defaultFrom fixture or assert an explicit capability error; the TypeScript runner currently exposes this as one known skip.
  • Inventory legacy extension points and stabilize their neutral replacements, exported types, and parser-specific options.
  • Implement the parser-neutral react-docgen facade and native CLI parser configuration.
  • Add package READMEs, migration guidance, release notes, and CLI configuration documentation.
  • Validate packed tarballs, clean consumer installs, declarations, package exports, changesets, and the supported Node/platform matrix before versioning.

Validation

Local validation after merging current main into next, using pnpm 12.16.1:

  • Frozen-lockfile installation passes.
  • Package builds and full ESLint pass.
  • All six package test suites pass with coverage: 1,835 tests passed, one known Babel-only skip.
  • The website production build, sitemap generation, and search indexing pass.

Packed-consumer validation and the full CI Node/platform matrix remain release-readiness work.

danez and others added 14 commits October 25, 2026 10:53
# Conflicts:
#	packages/react-docgen/vitest.config.ts
* fix: detect useCallback methods in imperative handles

* test: cover imperative handle callback references

* fix: document the function a useCallback method wraps

The handler recognised a useCallback-wrapped property as a method, but
the documentation was built from the call expression, so the method
came out with no parameters and no return type. resolveToMethodFunction
now resolves a value to the function it documents, unwrapping a React
useCallback call, and both the handler's method test and the
documentation builder use it. The tests assert the wrapped function's
signature and cover a local function named useCallback, a call with no
arguments and a non-function argument.

* test: cover every surface that documents a useCallback method

The handler's unwrapping now runs for every method node path, so add the
surfaces it reaches beyond the imperative handle identifier: a callback
written inline in the handle object, an ObjectExpression component, a
statics object, a class property and a Component.foo assignment, plus the
docblock and the async/generator modifiers of the wrapped function, a
renamed useCallback import and a handle that precedes the declaration.
The controls pin the guards the unwrapping keeps: a local useCallback, a
missing or non-function argument, a spread argument, a nested call and
useMemo.

* test: pin the zero-parameter, unannotated useCallback method

Every positive callback fixture carried a typed parameter and a return
annotation, so the empty-signature path the boundary ledger claims was
never exercised. Admit useCallback(() => {}, []) through
useImperativeHandle and assert an empty params list and a null return.

* chore: add changeset for the useCallback method fix

* test: run the reported component through the parser

The handler tests build a component definition and call the handler
directly, so none of them exercise the resolver path the issue reports:
a memo(forwardRef) component whose method is exposed as a shorthand
property. Parse that component verbatim through the public parse() and
assert the documented method, with the same component without
useCallback as a control for memo/forwardRef resolution.

* fix: keep the useCallback unwrapping on the imperative handle path

Documenting the function a useCallback call wraps changed every method
surface, including class fields and statics objects where hooks are not
valid React usage. Pass the imperative handle down to
getMethodDocumentation and unwrap only there; the other surfaces keep
documenting plain function values only. The surface tests become
controls: they assert that an object, statics and class-property
useCallback stays undocumented, next to a plain function on the same
surface that still is.

* test: drop the out-of-scope assignment case

findAssignedMethods only collects assignments whose right-hand side
resolves to a function, so Component.foo = useCallback(...) never
reaches getMethodDocumentation through the handler. Remove the unit test
that implied the assignment surface supported it.

* test: pin the lifecycle filter on an unwrapped callback

The imperative handle admission keeps the React lifecycle check, so a
handle property named after a lifecycle method stays undocumented even
when its value is a useCallback call. Control: it passes with and
without the unwrapping.

---------

Co-authored-by: askalf <askalf@users.noreply.github.com>
(cherry picked from commit a73f036)
Carry #1139 into the shared method handler and portable fixture corpus.
Resolve local callback bindings and React import aliases while keeping
useCallback unwrapping limited to imperative handles.

Co-authored-by: ALF <263217947+askalf@users.noreply.github.com>
* fix: parse parenthesized TypeScript function types

* style: remove blank line between test imports

---------

Co-authored-by: sean0407 <22557931+sean0407@users.noreply.github.com>
Co-authored-by: Daniel Tschinder <code@tschinder.de>
(cherry picked from commit 065e7c1)
Move the #1142 regression into the portable fixture corpus so legacy
and TypeScript extraction both verify the complete documentation output.

Co-authored-by: sean0407 <22557931+sean0407@users.noreply.github.com>
Regenerate the merged lockfile with pnpm 12.16.1 and retain the
parser-neutral workspace packages and original contribution credits.
Keep the shared #1142 fixture instead of restoring its duplicate test.
@changeset-bot

changeset-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c967779

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@react-docgen/core Patch
react-docgen Patch
@react-docgen/babel Patch
@react-docgen/hermes Patch
@react-docgen/typescript Patch
@react-docgen/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for react-docgen canceled.

Name Link
🔨 Latest commit c967779
🔍 Latest deploy log https://app.netlify.com/projects/react-docgen/deploys/6ae5d63a9f662938de968cf1

danez added 3 commits October 31, 2026 12:42
Replace the source-text regex flagged by CodeQL with one AST traversal
per component. Recognize real imports and require calls without treating
comments or string literals as imports.
Move the walker and visitors to module scope and pass extraction and
return-object data through per-traversal state. Verify independent
results across multiple handle factories and successive extractions.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants