feat(parser): build React fiber trees from source without running it - #116
Open
aidenybai wants to merge 31 commits into
Open
feat(parser): build React fiber trees from source without running it#116aidenybai wants to merge 31 commits into
aidenybai wants to merge 31 commits into
Conversation
…d linker Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ormance harness Adds the abstract interpreter (values, scopes, statements, calls, JSX, hooks, contexts), the static reconciler mirroring ReactChildFiber and ReactFiberBeginWork shapes, DOM host-config rules, serializable snapshots with an NFA-based consistency matcher, a bippy-backed runtime capture harness and a fixture conformance suite comparing both. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…unrolling and undecided side effects Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…tance, HOC display names and 14 hard fixtures - model regex literals as a static value kind; run pure String/RegExp methods for real when receiver and arguments are fully known, including replace() callbacks routed through the interpreter - class components: inherit render/lifecycle members, static defaultProps and contextType through project-local base classes - HOCs: displayName assignment renames wrapped functions/components, reduceRight composition, rest parameters - conditional array/object mutations wrap in conditional values via undecided control-flow frames; pop/shift/splice poison arrays when the outcome is undecided - fixtures: hocs, conditional-pushes, i18n, tagged-templates, router, forms, error-boundaries, compiled-automatic/classic, recursion, typescript-syntax, class-advanced, suspending-data, path-aliases - tsconfig paths + vite aliases for fixture path aliases; lint ignores compiled fixture output Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…er and tree renderer - matcher: drop redundant same-level attribute mismatches, list expected siblings in source order - interpreter: module bindings without an initializer are unknown, not undefined (declare const, later-assigned let) - statements: distinct continue completion so loops with continue still unroll; only a break that targets the loop itself disables unrolling - values: conditional() collapses nested conditionals on the same test and identical arms - fiber: Offscreen fibers have no owner, matching createFiberFromOffscreen Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…s from them findMountPoints locates createRoot(el).render(x), hydrateRoot(el, x) and the legacy ReactDOM.render(x, el), following roots stored in module bindings. Only callee chains rooted in module-level bindings are resolved, so locals of nested functions are never evaluated in module scope. The inspect CLI gains --entry to render an entry module the way the browser mounts it. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…n and model throw - Renders run under a wall-clock budget; exceeding it throws AnalysisTimeoutError instead of pegging the CPU on pathological inputs. - Recursion is only followed while every argument is fully known, and at most MAX_RECURSION_DEPTH activations deep. A recursive string algorithm (fractional-indexing's midpoint) previously fanned out to 2^24 calls. - throw is a completion of its own: throwing arms leave the render path and are dropped from branch merges, a catch handler becomes the only arm when the block is known to throw, and a function that always throws yields unknown. - Diagnostics are deduplicated per code, message and location. - Free identifiers are checked against the host's globals plus browser-only names before being reported as unresolved. - isNullish replaces loose == null comparisons. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… apps Adds the corpus module: 30 real-world repositories plus this monorepo's fixture apps are shallow-cloned, every exported component is rendered with unknown props, and the results are aggregated (fiber tags, unknown reasons, opaque packages, crashes, timeouts, unresolved imports, diagnostics) into JSON and markdown reports with rendered entry trees. With --live the target's dev server is started, a bippy capture script is injected into headless Chromium before the page loads, the committed fiber tree is snapshotted once React goes quiet, and it is matched against the tree derived statically from the entry module's mount point. Runtime snapshots now name built-in fibers the way React DevTools does. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ions and recursion cutoff by argument knowledge Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ain is unavailable, link workspace packages for corpus checkouts, model TS enums Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…every/includes, optional chaining short-circuit - OptionalValue models items a filter may drop; map/forEach/slice/reduce/ at and children reconciliation treat them as presence branches - selectItem reads filtered[k] as the k-th present item instead of unknown - find/findLast fall through undecided matches; some/every/includes decide from per-item verdicts - ?. short-circuits on nullish objects and callees along the access spine; logical operators distribute over branching left operands - callsHooksOrCreatesJsx filters PascalCase non-components (API handlers) Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- narrowing.ts: if/else arms, switch cases, ternaries, && / || and early returns refine variables and property paths (x, x.y, x?.y === "a") by dropping arms the path rules out; optional chains keep nullish prefixes where they would have yielded undefined - scopes gain a kind; narrowing scopes never receive writes - logical operators stay linear: the kept arm is filtered instead of distributing the right side over every arm (exponential on || chains) - find/findLast stop branching past SELECTION_LIMIT undecided candidates Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ot as unresolved A checkout without its dependencies installed resolved bare specifiers into this monorepo's own node_modules and analyzed a different version of the package. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…s static tree Also pass per-target environment variables to the dev server and scan vercel/ai-chatbot from its root, where its components live beside app/. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…eference equality, branch memo on a conditional inner type Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ss definitions, as NamedEvaluation does Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…l that cannot be followed Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…itional on not having returned, store negated tests as the positive one with swapped arms Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… name when matching Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…elopment build a bundler substitutes Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ctor functions, define class components from a member list, fold module-level class statics Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…earch known arrays with indexOf and findIndex, unroll for loops whose header assigns outer counters Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…, prototype methods via call/apply and Boolean/String callbacks are followed Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…typeof of React's wrappers, apply binary operators per conditional arm, fold every module-level static once a binding is declared Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… a path decides it Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ctions and wrappers rules out, keep named members written onto arrays Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…e analysis never runs, or a computed key, may have written Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
aidenybai
marked this pull request as ready for review
September 6, 2026 09:37
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.
Summary
Adds
@bippy/parser, a private workspace package that constructs a React fiber tree directly from a project's source. It parses withoxc-parser, links modules withoxc-resolver, abstractly interprets component render bodies, and reconciles the resulting elements the wayReactChildFiber/ReactFiberConfigDOMdo. Every tree can be checked against what React actually commits, captured through Bippy's hook, and the package ships the harness and corpus tooling that does so.packages/parser/README.mddocuments the pipeline, the static value model, the harness, the corpus and the known limits.What is in the package
src/module,src/project,src/link— parsing, tsconfig-aware resolution (paths, extension aliases, unresolvableextends, workspace symlinks, CommonJS interop, JSON modules), cross-module symbol linking, React API identification regardless of import style, and module-level compound-component statics.src/analyze— the abstract interpreter overStaticValues: conditionals keyed by test text with collapse/normalization, path-sensitive narrowing (truthiness, nullish, equality, switch cases, and every local value that branched on a decided test), undecided side effects, bounded loop unrolling, lazy module bindings, function/class/memo/forwardRef/lazy/context components (including Babel/TS-lowered classes), hooks and contexts, React's element shape and$$typeofbrands per React version, standard globals as values (Object.assign,Array.prototype.slice.call,Symbol,process.env), array/string/regexp builtins,in,typeof, optional chaining, TS enums, closure escape invalidation, recursion cutoff and a wall-clock budget.src/fiber— static reconciliation intoStaticFibers: fragments,textContentvsHostText, Suspense fallbacks, hoistables/singletons,Childrenkey algorithm, render/recursion/fiber budgets.src/snapshot— a serializableFiberSnapshotshared by static and runtime sides, a tree printer, and an automaton-based matcher: unknown nodes are wildcards, branches are alternations, lists are repetitions; among accepting paths the one explaining the most runtime fibers wins, giving acoveragemetric so a match cannot be bought with wildcards.src/harness(@bippy/parser/harness) — renders for real under happy-dom with Bippy observing commits and verifies static against runtime.src/corpus(@bippy/parser/corpus) — 30 real React repositories with framework/app-directory/entry metadata, a static scanner, and live verification (dev server boot, Playwright capture with Bippy's hook injected before React loads, comparison against the largest committed root).scripts/inspect.ts,scripts/corpus.ts— CLIs.Verification
tests/unit(interpreter semantics viadescribeValue, linker, resolver, matcher, JSX text, host rules, mount discovery, tree rendering).tests/fixtures(conditionals, lists, context, class components, error boundaries, hooks, HOCs, Suspense, compiled classic/automatic/class output, path aliases, i18n, router, forms, …) render both statically and for real; the runtime tree must be one the static tree describes and, unless a fixture opts out withminCoverage, every runtime fiber must be explained by a concrete static fiber.Low coverage on the router apps is honest: their trees are dominated by router internals whose state (matches, loaders, transitions) is unknowable statically; the match still holds. Remaining kitchen-sink unknowns are by design (forgotten hook/class state, Proxy-built
motion.*/styled.*factories,Map-held contexts).pnpm typecheck,pnpm check, andpnpm test(1,870 tests) pass. A knip pass removed the only dead export.Root changes
vite.config.ts: registers the parser test project and excludes compiled fixtures from formatting.package.json:typecheckincludes the parser..changeset/config.json: ignores@bippy/parser(private).Known limits
X.m = …andObject.assign(X, {…})never run; bindings they mention read unknown statics rather than a wrongundefined.Proxy,Map/Setcontents,Object.createprototypes and most host APIs are unknown.How to try it
pnpm --filter @bippy/parser test pnpm --filter @bippy/parser inspect packages/parser/tests/fixtures/conditionals.tsx --diagnostics pnpm --filter @bippy/parser corpus --workspace --live --no-scan @bippy/e2e-kitchen-sink pnpm --filter @bippy/parser corpus shadcn-ui/ui