Compare commits

...

132 Commits

Author SHA1 Message Date
Mike Vitousek
53f7b222ee [compiler][wip] Track and propagate function effects of arrays and objects
[ghstack-poisoned]
2024-08-02 01:55:26 -07:00
Mike Vitousek
38ddcbe898 Update on "[compiler] Allow global mutation effects in arguments passed to hooks and in return values"
In investigating the results of #30572 we discovered a lot of false positives from when functions containing possibly-effectful calls were returned from hooks or passed into hooks. This corresponds to similar issues for GlobalMutations, because the effectful call work uses the same FunctionEffect infra. This fixes the infra to allow GlobalMutation function effects in return and throw terminals and in all hook arguments.


[ghstack-poisoned]
2024-08-02 01:55:24 -07:00
Mike Vitousek
317484f7ef [compiler] Allow global mutation effects in arguments passed to hooks and in return values
[ghstack-poisoned]
2024-08-02 00:09:34 -07:00
Sebastian Markbåge
06d0b89e8d [DevTools] Enable pointEvents while scrolling (#30560)
[`react-window` disables `pointerEvents` while scrolling meaning you
can't click anything while
scrolling.](https://github.com/bvaughn/react-window/issues/128).

This means that the first click when you stop the scroll with inertial
scrolling doesn't get registered. This is suuuper annoying. This might
make sense when you click to stop on a more intentional UI but it
doesn't makes sense in a list like this because we eagerly click things
even on mousedown.

This PR just override that to re-enable pointer events.

Supposedly this is done for performance but that might be outdated
knowledge. I haven't observed any difference so far.

If we discover that it's a perf problem, there's another technique we
can use where we call `ownerDocument.elementFromPoint(e.pageX, e.pageY)`
and then dispatch the event against that element. But let's try the
simplest approach first?
2024-08-01 11:34:38 -04:00
Sebastian Markbåge
4ea12a11d1 [DevTools] Make Element Inspection Feel Snappy (#30555)
There's two problems. The biggest one is that it turns out that Chrome
is throttling looping timers that we're using both while polling and for
batching bridge traffic. This means that bridge traffic a lot of the
time just slows down to 1 second at a time. No wonder it feels sluggish.
The only solution is to not use timers for this.

Even when it doesn't like in Firefox the batching into 100ms still feels
too sluggish.

The fix I use is to batch using a microtask instead so we can still
batch multiple commands sent in a single event but we never artificially
slow down an interaction.

I don't think we've reevaluated this for a long time since this was in
the initial commit of DevTools to this repo. If it causes other issues
we can follow up on those.

We really shouldn't use timers for debouncing and such. In fact, React
itself recommends against it because we have a better technique with
scheduling in Concurrent Mode. The correct way to implement this in the
bridge is using a form of back-pressure where we don't keep sending
messages until we get a message back and only send the last one that
matters. E.g. when moving the cursor over a the elements tab we
shouldn't let the backend one-by-one move the DOM node to each one we
have ever passed. We should just move to the last one we're currently
hovering over. But this can't be done at the bridge layer since it
doesn't know if it's a last-one-wins or imperative operation where each
one needs to be sent. It needs to be done higher. I'm not currently
seeing any perf problems with this new approach but I'm curious on React
Native or some thing. RN might need the back-pressure approach. That can
be a follow up if we ever find a test case.

Finally, the other problem is that we use a Suspense boundary around the
Element Inspection. Suspense boundaries are for things that are expected
to take a long time to load. This shows a loading state immediately. To
avoid flashing when it ends up being fast, React throttles the reveal to
200ms. This means that we take a minimum of 200ms to show the props. The
way to show fast async data in React is using a Transition (either using
startTransition or useDeferredValue). This lets the old value remaining
in place while we're loading the next one.

We already implement this using `inspectedElementID` which is the async
one. It would be more idiomatic to implement this with useDeferredValue
rather than the reducer we have now but same principle. We were just
using the wrong ID in a few places so when it synchronously updated they
suspended. So I just made them use the inspectedElementID instead.

Then I can simply remove the Suspense boundary. Now the selection
updates in the tree view synchronously and the sidebar lags a frame or
two but it feels instant. It doesn't flash to white between which is
key.
2024-08-01 11:04:56 -04:00
Sebastian Silbermann
88ee14ffa5 [Devtools] Ensure initial read of useFormStatus returns NotPendingTransition (#28728) 2024-08-01 10:55:53 +02:00
Mike Vitousek
56dbd58feb [compiler] More complete validation against locals being reassigned after render
Summary:
This diff extends the existing work on validating against locals being reassigned after render, by propagating the reassignment "effect" into the lvalues of instructions when the rvalue operands include values known to cause reassignments. In particular, this "closes the loop" for function definitions and function calls: a function that returns a function that reassigns will be considered to also perform reassignments, but previous to this we didn't consider the result of a `Call` of a function that reassigns to itself be a value that reassigns.

This causes a number of new bailouts in test cases, all of which appear to me to be legit.

ghstack-source-id: 770bf02d079ea2480be243a49caa6f69573d8092
Pull Request resolved: https://github.com/facebook/react/pull/30540
2024-07-31 11:11:07 -07:00
Josh Story
a7d1240c96 [Fizz] Update postpone abort semantics when prerendering (#30541)
When aborting with a postpone value in Fizz if any tasks are still
pending in the root while prerendering the prerender will fatally error.
This is different from postponing imperatively in a root task and really
the semantics should be the same. This change updates React to treat an
abort with a postpone value as a postponed root rather than a fatal
error.
2024-07-31 08:33:43 -07:00
Sebastian Markbåge
2b00018347 [DevTools] Track the parent DevToolsInstance while mounting a tree (#30542)
This just tracks the `.parent` field properly and uses DevToolsInstances
in more places that used to use IDs or Fibers.

I also use this new parent path when looking up a DevToolsInstance from
a DOM node. This should ideally be simple because the `.parent` field
represents only the unfiltered parents and include any virtual parents.
So we should be able to just get one from nearest Fiber that has one.

However, because we don't currently always clean up the map of
DevToolsInstances (e.g. updateComponentFilters doesn't recursively clean
out everything) it can leave matches hanging that shouldn't be there. So
we need to run the shouldFilterFiber filter to ignore those.

Another interesting implication is that without a FiberInstance we don't
have a way to get to a VirtualInstance from a HostComponent. Which means
that even filtered Fibers need to have a FiberInstance if they have a
VirtualInstance parent. Even if we don't actually mount them into the
front-end.
2024-07-31 10:07:17 -04:00
Hendrik Liebau
47069f0e80 Build all bundles required by flight fixture with build-for-flight-dev (#30534) 2024-07-31 15:02:36 +02:00
Sebastian Markbåge
12e9579099 [Flight] Enable owner stacks on the client when replaying logs (#30473)
There's a special case that happens when we replay logs on the client
because this doesn't happen within the context of any particular
rendered component. So we need to reimplement things that would normally
be handled by a full client like Fiber.

The implementation of `getOwnerStackByComponentInfoInDev` is the
simplest version since it doesn't have any client components in it so I
move it to `shared/`. It's only used by Flight but both `react-server/`
and `react-client/` packages. The ReactComponentInfo type is also more
generic than just Flight anyway.

In a follow up I still need to implement this in React DevTools when
native tasks are not available so that it appends it to the console.
2024-07-31 07:56:15 -04:00
Jack Pope
3f8b1333da Set enableLazyContextPropagation to dynamic on RN (#30516) 2024-07-30 18:31:31 -04:00
dan
96f3093bec Remove RefreshRuntime.findAffectedHostInstances (#30538)
I originally added this with a plan to visualize which nodes got updated
after a Fast Refresh. I didn't end up implementing that part, and to my
knowledge, no actively used integration actually does that or use this
method.

- [Webpack plugin doesn't use
it](f1c8b9a441/lib/runtime/RefreshUtils.js)
- [RN doesn't use
it](23c5c42de0/packages/react-native/Libraries/Core/setUpReactRefresh.js)
- [Global GitHub code
search](https://github.com/search?q=findAffectedHostInstances&type=code&p=1)
only shows copies of this code and the type definition based on it, but
not actual calls to it

We should be able to delete this without a problem.
2024-07-30 22:06:16 +01:00
Lauren Tan
d06196c1cd [compiler] Visit nested scopes in pruned scopes in PromoteUsedTemporaries
While debugging #30536 I happened to notice that the bug only reproduced
when there was interleaving scopes, and observed that an unpruned scope
nested inside of a pruned one was not being visited by
CollectPromotableTemporaries, which keeps track of which identifiers
should be promoted later. Therefore when actually promoting temporaries
we were skipping over the identifiers in children of pruned scopes

ghstack-source-id: d805f62f22
Pull Request resolved: https://github.com/facebook/react/pull/30537
2024-07-30 17:05:23 -04:00
Mofei Zhang
edfaa99f01 [compiler][repro] JSX tag local variable is named lowercase
ghstack-source-id: f9ac4641e171a1ae3450f733fef71be346447866
Pull Request resolved: https://github.com/facebook/react/pull/30536
2024-07-30 16:32:09 -04:00
Mofei Zhang
212d5ae8cb [compiler][repro] fixtures for fbt plural and macro bugs
ghstack-source-id: 8ccf49bb40cd634932b84dd637439042aa60fd46
Pull Request resolved: https://github.com/facebook/react/pull/30535
2024-07-30 16:32:09 -04:00
Mofei Zhang
704c34e21f [compiler] Bail out on local variables named 'fbt'
ghstack-source-id: c4e2b802a029b9dd7941ebcdeaaf471ddd95f868
Pull Request resolved: https://github.com/facebook/react/pull/30524
2024-07-30 16:32:09 -04:00
Mofei Zhang
7c8734c41c [compiler][repro] Fbt local var incompatibility repro
ghstack-source-id: ec256a365bdcef7c10da2af2b58f3931e259ad65
Pull Request resolved: https://github.com/facebook/react/pull/30523
2024-07-30 16:32:09 -04:00
Hendrik Liebau
3208e73e82 Assign resolved outlined props to element object (and not only tuple) (#30528)
Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>
2024-07-30 21:31:32 +02:00
Sebastian Markbåge
f827c792ea [DevTools] Add DevToolsInstance to Store Stateful Information (#30517)
Stacked on #30494 and #30491.

This is setting us up to be able to track Server Components. This is now
split into a FiberInstance (Client Component) and a VirtualInstance
(Server Component). We're not actually creating any VirtualInstances yet
though this is just the data structures.

Server Components and potentially other compiled away or runtime
optimized away (e.g. calling through a function without creating an
intermediate Fiber) don't have a stateful instance. They just represent
the latest data. It's kind of like a React Element.

However, in the DevTools UI we need them to be stateful partly just so
that you can select and refer to them separately. E.g. the same Server
Component output rendered into different slots on the client should
still have two different representations in the DevTools. Also if the
same child Fibers update in place because the Server Component refreshed
we shouldn't lose the selection if you've selected a Server Component.

I'll implement this by creating Virtual Instances that only exist for
the purpose of the DevTools UI and so it'll be implemented in the
DevTools.

We could just make a Map from `id` to `Fiber | ReactComponentInfo` but
that requires a branching without a consistent hidden class. We also
need some more states on there. We also have some other Maps that tracks
extra states like that of component stacks, errors and warnings.
Constantly resizing and adding/removing from a Map isn't exactly fast.
It's faster to have a single Map with an object in it than one Map per
object. However, having extra fields that are usually just `null` can
instead mean more memory gets used. Since only a very small fraction of
instances will have errors/warnings or having initialized its component
stack, it made sense to store that in a separate Map that is usually
just empty.

However, with the addition of particularly the `parent` field and the
ability to do a fast hidden-class safe branching on the `kind` field I
think it's probably worth actually allocating an extra first class
object per Instance to store DevTools state into. That's why I converted
from just storing `Fiber` -> `id` to storing `Fiber` ->
`DevToolsInstance` which then keeps the warnings/errors/componentStack
as extra fields that are usually `null`. That is a lot of objects though
since it's one per Fiber pair basically.
2024-07-30 15:15:45 -04:00
Sebastian Markbåge
146df7c311 [Fiber] Make DevTools Config use Static Injection (#30522)
We use static dependency injection. We shouldn't use this dynamic
dependency injection we do for DevTools internals. There's also meta
programming like spreading and stuff that isn't needed.

This moves the config from `injectIntoDevTools` to the FiberConfig so it
can be statically resolved.

Closure Compiler has some trouble generating optimal code for this
anyway so ideally we'd refactor this further but at least this is better
and saves a few bytes and avoids some code paths (when minified).
2024-07-30 15:03:54 -04:00
Sebastian Markbåge
f963c80d21 [DevTools] Implement "best renderer" by taking the inner most matched node (#30494)
Stacked on #30491.

When going from DOM Node to select a component or highlight a component
we find the nearest mounted ancestor. However, when multiple renderers
are nested there can be multiple ancestors. The original fix #24665 did
this by taking the inner renderer if it was an exact match but if it
wasn't it just took the first renderer.

Instead, we can track the inner most node we've found so far. Then get
the ID from that node (which will be fast since it's now a perfect
match). This is a better match.

However, the main reason I'm doing this is because the old mechanism
leaked the `Fiber` type outside the `RendererInterface` which is
supposed to abstract all of that. With the new algorithm this doesn't
leak.

I've tested this with a new build against the repro in the old issue
#24539 and it seems to work.
2024-07-30 14:57:26 -04:00
Jan Kassens
41ecbada0e Enable prettier for some blocklisted fixtures with invalid GraphQL (#30425)
The invalid GraphQL in these fixtures somehow causes an unhandled
promise rejection error when running `yarn prettier-all`. This fixes
that issue by making the GraphQL valid.
2024-07-30 14:23:43 -04:00
Lauren Tan
ab7c166313 [ci] Fix incorrect polling
Oops. Actually poll correctly by fetching the latest workflow run each
retry, and not just checking the first attempt.

Test plan: https://github.com/facebook/react/actions/runs/10165564989

ghstack-source-id: 3202d8f6ae
Pull Request resolved: https://github.com/facebook/react/pull/30531
2024-07-30 11:54:48 -04:00
Lauren Tan
73b21a05e3 [ci] Read node version from .nvmrc instead of hardcoding
ghstack-source-id: 336ac1a45f
Pull Request resolved: https://github.com/facebook/react/pull/30530
2024-07-30 11:54:48 -04:00
Lauren Tan
a7f3b49be2 [ci] Make some workflows dispatchable
The previous checks for prerelease_commit_sha was incorrectly
implemented for devtools regression tests so I fixed it. I also made
some other workflows dispatchable so they can be manually run from
GitHub's UI as needed.

Test plan: https://github.com/facebook/react/actions/runs/10165564989

ghstack-source-id: b30c7b48e7
Pull Request resolved: https://github.com/facebook/react/pull/30529
2024-07-30 11:54:48 -04:00
Lauren Tan
927ba5799a [rfc] Delete devtools_check_repro workflow
With GitHub issue templates this workflow is not truly necessary and
can deny other workflows from running due to a limited amount of CI
workers in the pool.

I propose deleting this workflow and relying on issue templates instead.

ghstack-source-id: a798621f36
Pull Request resolved: https://github.com/facebook/react/pull/30518
2024-07-30 10:57:35 -04:00
Sebastian Markbåge
33e54fa252 [DevTools] Rename NativeElement to HostInstance in the Bridge (#30491)
Stacked on #30490.

This is in the same spirit but to clarify the difference between what is
React Native vs part of any generic Host. We used to use "Native" to
mean three different concepts. Now "Native" just means React Native.

E.g. from the frontend's perspective the Host can be
Highlighted/Inspected. However, that in turn can then be implemented as
either direct DOM manipulation or commands to React Native. So frontend
-> backend is "Host" but backend -> React Native is "Native" while
backend -> DOM is "Web".

Rename NativeElementsPanel to BuiltinElementsPanel. This isn't a React
Native panel but one part of the surrounding DevTools. We refer to Host
more as the thing running React itself. I.e. where the backend lives.
The runtime you're inspecting. The DevTools itself needs a third term.
So I went with "Builtin".
2024-07-30 09:12:12 -04:00
Sebastian Markbåge
bea5a2bc46 [Fiber/DevTools] Stop injecting findHostInstanceByFiber (#30519)
This is not used by DevTools since it has its own implementation of it.

This function is getting removed since `findDOMNode` is getting removed
so we shouldn't keep around extra bytes unnecessarily.

There is also `findHostInstancesForRefresh` which should really be
implemented on the `react-refresh` side. Not using an injection but
that's a heavier lift and only affects `__DEV__`.
2024-07-29 20:05:14 -04:00
Lauren Tan
3043e1ae21 [ci] Add polling to download-build-artifacts
Several CI workflows depend on the runtime_build_and_test.yml workflow
to complete before it can successfully download the build artifact.
However it is possible to encounter a race condition where the build
hasn't completed when the new workflow is started.

This PR adds a simple polling mechanism that waits up to 10 minutes for
the build for that revision to complete.

ghstack-source-id: 6a954638a800fbea8081e6fba35ee4b4437731c5
Pull Request resolved: https://github.com/facebook/react/pull/30515
2024-07-29 19:18:04 -04:00
Lauren Tan
d98f1a4bc6 [ci] Deprecate yarn publish-prereleases
Added instructions on the new workflow. Let's keep this short around for
now as it may be used again due to muscle memory and emit a helpful
message instead.

ghstack-source-id: 3f840a3d4319309d31cefeae028f97d280b0c09a
Pull Request resolved: https://github.com/facebook/react/pull/30510
2024-07-29 19:18:04 -04:00
Lauren Tan
8fe510752f [ci] Cleanup more references to circleci
ghstack-source-id: 85a5f17b2b9dee35bb747ce2da13bffaed0fa34a
Pull Request resolved: https://github.com/facebook/react/pull/30509
2024-07-29 19:18:03 -04:00
Lauren Tan
8ebe42f17c [ci] Cleanup --ci flag
This can be restored back to being a boolean instead of an enum

ghstack-source-id: aca58fb7ea386ee489dd895e028f1aa2fc507193
Pull Request resolved: https://github.com/facebook/react/pull/30508
2024-07-29 19:18:03 -04:00
Lauren Tan
e415b22494 [ci] Rename scripts/circleci to scripts/ci
ghstack-source-id: 5d9cb7d3dbd0f5c54856ad194517524f14cf1ccb
Pull Request resolved: https://github.com/facebook/react/pull/30507
2024-07-29 19:18:03 -04:00
Lauren Tan
70885cfebe [ci] Cleanup forked build files
Unforks these scripts now that we are fully migrated to GH.

ghstack-source-id: e1e15452f2d2e178a5b56203ebd0b42151e6a9ba
Pull Request resolved: https://github.com/facebook/react/pull/30506
2024-07-29 19:18:02 -04:00
Jan Kassens
6b82f3c904 [RN] experiment to move Fabric completeWork to the commit phase (#30513)
There is currently a mismatch in how the persistent mode JS API and the
Fabric native code interpret `completeRoot`.

This is a short-lived experiment to see the effect of moving the Fabric
`completeRoot` call from `finalizeContainerChildren` to
`replaceContainerChildren` which in some cases does not get called.
2024-07-29 18:38:55 -04:00
Lauren Tan
87b1a942ef [ci] Override actions/cache default timeout
actions/cache has a default timeout of 10 minutes. Occasionally the
cache service download gets stuck and it waits this amount of time
before proceeding like it was a cache miss.

10 minutes is way too long so let's shorten this to a minute.

ghstack-source-id: 95dee31bd9
Pull Request resolved: https://github.com/facebook/react/pull/30512
2024-07-29 17:36:43 -04:00
Jack Pope
397646ad51 Update enableLazyContextPropagation flag (#30514)
- Flag set to true for FB
- Flag set to experimental for OSS
2024-07-29 16:50:28 -04:00
Josh Story
a451de014c [Fizz] Allow aborting during render (#30488)
Currently if you abort a Fizz render during rendering the render will
not complete correctly because there are inconsistencies with task
counting. This change updates the abort implementation to allow you to
abort from within a render itself. We already landed a similar change
for Flight in #29764
2024-07-29 13:18:15 -07:00
Lauren Tan
fe68112240 [ci] Temporarily always build from source for codesandbox
We need a GitHub token to download artifacts from GitHub so
unfortunately codesandboxci will need to revert to the slower process
of building from source for now until it's possible to pass secrets to
codesandboxci.

ghstack-source-id: edab979084
Pull Request resolved: https://github.com/facebook/react/pull/30511
2024-07-29 16:16:09 -04:00
Sebastian Markbåge
ec98d36c3a [DevTools] Rename Fiber to Element in the Bridge Protocol and RendererInterface (#30490)
I need to start clarifying where things are really actually Fibers and
where they're not since I'm adding Server Components as a separate type
of component instance which is not backed by a Fiber.

Nothing in the front end should really know anything about what kind of
renderer implementation we're inspecting and indeed it's already not
always a "Fiber" in the legacy renderer.

We typically refer to this as a "Component Instance" but the front end
currently refers to it as an Element as it historically grew from the
browser DevTools Elements tab.

I also moved the renderer.js implementation into the `backend/fiber`
folder. These are at the same level as `backend/legacy`. This clarifies
that anything outside of this folder ideally shouldn't refer to a
"Fiber".

console.js and profilingHooks.js unfortunately use Fibers a lot which
needs further refactoring. The profiler frontend also uses the term
alot.
2024-07-29 14:29:52 -04:00
Lauren Tan
941e1b4a0a [ci] Fix passing secrets to runtime_prereleases
Second try, seems like you need to explicitly pass secrets to reusable
workflows.

ghstack-source-id: 05ffdae13474dba64119182bf72c15d07f83efd2
Pull Request resolved: https://github.com/facebook/react/pull/30504
2024-07-29 14:27:21 -04:00
Josh Story
9938e248fe [Fizz] Don't perform work when closing (#30497)
When a Fizz render is closing but not yet closed it's possible that
pinged tasks can spawn more work. The point of the closing state is to
allow time to start piping/reading the underlying stream but
semantically the render is finished at that point so work should no
longer happen.
2024-07-29 11:09:54 -07:00
Lauren Tan
93b12b824c [ci] Fix prerelease script npm authtoken
Try again to specify the npm token so the publish script can succeed.

ghstack-source-id: cd6e01b2ea
Pull Request resolved: https://github.com/facebook/react/pull/30503
2024-07-29 13:46:57 -04:00
Lauren Tan
4e482f4904 [ez] Remove circleci badge from readme
CircleCI is no longer in use in this repo: a6b5ed01ae

ghstack-source-id: b6234f11d143d2c65832e17ccf180b255dc23c9d
Pull Request resolved: https://github.com/facebook/react/pull/30502
2024-07-29 13:26:14 -04:00
Mofei Zhang
a8f5c4e16d [compiler] patch: retain UpdateExpression for globals
ghstack-source-id: aff6606f85698ccda13d1deffb6faa9d91f9821a
Pull Request resolved: https://github.com/facebook/react/pull/30471
2024-07-29 13:18:45 -04:00
Mofei Zhang
2a9274a73e [compiler] Repro: updates to globals should be retained
ghstack-source-id: 7ee813c0d72e3c752b299f63bfcec3647b022544
Pull Request resolved: https://github.com/facebook/react/pull/30470
2024-07-29 13:18:45 -04:00
Mofei Zhang
e215aa1160 [compiler] Fix FBT whitespace handling again (again (again))
ghstack-source-id: 00a86e41cfb8a6fb56b7fcd811740d1d9b89a611
Pull Request resolved: https://github.com/facebook/react/pull/30451
2024-07-29 13:18:45 -04:00
Mofei Zhang
e0acd77aab [compiler] Todo for fbt with multiple pronoun/plural
ghstack-source-id: 77b6980c2b080771d18aa275c3b3258884bd463c
Pull Request resolved: https://github.com/facebook/react/pull/30437
2024-07-29 13:18:45 -04:00
Mofei Zhang
a15e8d7cdc [compiler][repro] Test fixture for fbt plural bug
ghstack-source-id: 222e7312c081faf36702a1c790a4e336b5bcc928
Pull Request resolved: https://github.com/facebook/react/pull/30432
2024-07-29 13:18:45 -04:00
Lauren Tan
28b7f0bb71 [ci] Remove references to CIRCLE_NODE_{TOTAL,INDEX}
These env variables were used for circlci parallelization and can now be
removed.

ghstack-source-id: 224c47194d
Pull Request resolved: https://github.com/facebook/react/pull/30501
2024-07-29 13:04:00 -04:00
Lauren Tan
164af898c4 [ci] Remove jestSequencer
This was previously used for circleci parallelization but is no longer
needed.

ghstack-source-id: d8c1005e09
Pull Request resolved: https://github.com/facebook/react/pull/30500
2024-07-29 13:04:00 -04:00
Lauren Tan
39acfdb3be [ci] Fix ci prep script
During params parsing for this script, it previously would call out to
CircleCI for a build ID, but this is no longer needed.

ghstack-source-id: 9c70824498
Pull Request resolved: https://github.com/facebook/react/pull/30499
2024-07-29 13:03:59 -04:00
Lauren Tan
03072a7a11 [ci] Fix prereleases reusable workflow
Turns out I had configured the reusable workflow with the wrong `on`
command.

Also removes the workflow_dispatch config from the nightly workflow as
that was not meant to be triggered manually.

ghstack-source-id: 426d07279d
Pull Request resolved: https://github.com/facebook/react/pull/30498
2024-07-29 12:37:05 -04:00
Lauren Tan
a6b5ed01ae [ci] Delete .circleci
Everything has been migrated to GH. It's time to say goodbye...

ghstack-source-id: c7a9f49a72
Pull Request resolved: https://github.com/facebook/react/pull/30496
2024-07-29 11:29:29 -04:00
Lauren Tan
9af9764f55 [ci] Add prerelease workflows to GH actions
Migrates the last 2 remaining circleci jobs to GH actions. The behavior
of these workflows have been kept the same.

Overview:
- Reusable workflow `runtime_prereleases.yml` added
- Nightly workflow on cron triggers the reusable workflow with the
  current HEAD sha
- Manual workflow which can be triggered from the github UI with a
  `prerelease_commit_sha` which triggers the reusable workflow

ghstack-source-id: 84ef33c732
Pull Request resolved: https://github.com/facebook/react/pull/30495
2024-07-29 11:29:29 -04:00
Josh Story
d17e9d1ce5 [Fizz] Prerender fallbacks before children (#30483)
When prerendering it can be convenient to abort the prerender while
rendering. However if any Suspense fallbacks have not yet rendered
before the abort happens the fallback itself will error and cause the
nearest parent Suspense boundary to render a fallback instead.
Prerenders are by definition not time critical so the prioritization of
children over fallbacks which makes sense for render isn't similarly
motivated for prerender. Given this, this change updates fallback
rendering during a prerender to attempt the fallback before attempting
children.
2024-07-26 14:06:47 -07:00
Lauren Tan
b9af819f8b [ci] Fix invalid username and email in synthetic commit
ghstack-source-id: 951d0ef445
Pull Request resolved: https://github.com/facebook/react/pull/30486
2024-07-26 15:15:00 -04:00
Lauren Tan
c00e895c19 [ci] Update prepare-release-from-ci to dl from GH
Updates this script to download from GH actions instead, to prepare for
moving the cron jobs over to GH actions.

ghstack-source-id: 9bba9f2721e42b508d7fac6604be72906dedd836
Pull Request resolved: https://github.com/facebook/react/pull/30485
2024-07-26 14:57:52 -04:00
Lauren Tan
0d022861d2 [ci] Refactor download-build-artifacts
Extracts out the code to download builds from GH into its own module so
that it can be reused later.

ghstack-source-id: 26687db971d06339d099d1d5075825efb82cf6b8
Pull Request resolved: https://github.com/facebook/react/pull/30484
2024-07-26 14:57:51 -04:00
Jack Pope
1350a85980 Add unstable context bailout for profiling (#30407)
**This API is not intended to ship. This is a temporary unstable hook
for internal performance profiling.**

This PR exposes `unstable_useContextWithBailout`, which takes a compare
function in addition to Context. The comparison function is run to
determine if Context propagation and render should bail out earlier.
`unstable_useContextWithBailout` returns the full Context value, same as
`useContext`.

We can profile this API against `useContext` to better measure the cost
of Context value updates and gather more data around propagation and
render performance.

The bailout logic and test cases are based on
https://github.com/facebook/react/pull/20646

Additionally, this implementation allows multiple values to be compared
in one hook by returning a tuple to avoid requiring additional Context
consumer hooks.
2024-07-26 14:38:24 -04:00
Ahmed Abdelbaset
f7ee804c22 [compiler] Add git info to package.json files in compiler packages (#30475)
This PR adds the repository field to `compiler/packages/*/package.json`

| eslint-plugin-react-compiler | eslint-plugin-react-hooks |
| --- | --- |
|
![image](https://github.com/user-attachments/assets/3392a496-1ff1-4f36-ab96-cfbe1ed88693)
|
![image](https://github.com/user-attachments/assets/b0605dba-eef7-44fe-9484-979b4814d9fb)
|
2024-07-26 12:56:39 -04:00
Lauren Tan
c5f3a6f42f [circleci] Remove build_and_test workflow
Now that we've fully migrated the PR CI workflow (build_and_test) to GH,
we can fully delete the workflow altogether. This will break
https://react-builds.vercel.app/ unfortunately but I'll wait for
acdlite to come back from vacation and work with him to fix it.

The remaining jobs in circleci are for publishing prereleases of React
to npm. I'll work on migrating those next.

ghstack-source-id: 05f346829f
Pull Request resolved: https://github.com/facebook/react/pull/30480
2024-07-26 11:58:20 -04:00
Lauren Tan
0023cff871 [ci] Update runtime_commit_artifacts step versions
These were outdated and emitting warnings

ghstack-source-id: 2e22c29253
Pull Request resolved: https://github.com/facebook/react/pull/30479
2024-07-26 11:58:20 -04:00
Lauren Tan
2883d28b2d [ci] Unfork runtime_commit_artifacts
Promotes v2 to the primary workflow file so that we don't double write
to the protected branches.

Note: this may break DiffTrain temporarily, I will fix forward if so

ghstack-source-id: f6505a72f2
Pull Request resolved: https://github.com/facebook/react/pull/30477
2024-07-26 11:34:26 -04:00
Lauren Tan
417e93ca19 [ci] Add commit steps to new runtime_commit_artifacts_v2
Adds back the missing steps with a few tweaks to where previously some
`github` context value was referenced, I changed it to read from the
triggering workflow_run (ie the build on `main`) instead.

ghstack-source-id: 4b0fa135f0
Pull Request resolved: https://github.com/facebook/react/pull/30476
2024-07-26 11:34:25 -04:00
Josh Story
7f217d1d88 [Fiber] use srcset to trigger load even on img mount (#30351)
In https://github.com/facebook/react/pull/23316 we fixed a bug where
onload events were missed if they happened too early. This update adds
support for srcset to retrigger the load event. Firefox unfortunately
does not trigger a load even when you assign srcset so this won't work
in every browser when you use srcset without src however it does close a
gap in chrome at least
2024-07-25 15:46:45 -07:00
Lauren Tan
27d5dba884 [ci] Temporary fork runtime_commit_artifacts
Unfortunately creating a workflow that depends on another worfklow run
requires it to first be merged into main, so I can't really test porting
this without landing it first.

To do this safely, I've left the original job intact for DiffTrain and
added a forked file. This fork only currently downloads the artifact
from the HEAD commit in GH actions; I've removed the steps that push to
the protected branches for now while I test to see if this works as
expected.

This workflow needs to depend on the runtime_build_and_test workflow
being complete because otherwise it will fail since the artifacts
haven't been built yet.

ghstack-source-id: 0f9cebc525
Pull Request resolved: https://github.com/facebook/react/pull/30472
2024-07-25 18:09:32 -04:00
Lauren Tan
720a982a1f [ci] Remove devtools_regression_tests workflow from circleci
This has now been migrated to GH actions.

ghstack-source-id: 2dcc9ae5ad
Pull Request resolved: https://github.com/facebook/react/pull/30468
2024-07-25 17:27:38 -04:00
Lauren Tan
fe225e3a1f [ci] Add devtools regression workflow
More or less a straight copy from the circleci config. I spotted some
inefficiencies but will fix those later to make reviewing this easier.

ghstack-source-id: cb3456c602
Pull Request resolved: https://github.com/facebook/react/pull/30406
2024-07-25 17:27:38 -04:00
Lauren Tan
0a76aecbfa [ci] Cleanup run_devtools_e2e_tests
This was only used for build_and_test which has since been migrated to
gh actions and is now therefore unused. Looks like I missed this during
the previous cleanup.

ghstack-source-id: 278443951e
Pull Request resolved: https://github.com/facebook/react/pull/30467
2024-07-25 17:09:12 -04:00
Lauren Tan
a0e7ecfc6a Bump version to 0.0.0-experimental-ab3118d-20240725 2024-07-25 15:46:47 -04:00
Lauren Tan
83035ee299 Bump version to 0.0.0-experimental-9ed098e-20240725 2024-07-25 15:46:46 -04:00
Lauren Tan
789c257d79 Bump version to 0.0.0-experimental-334f00b-20240725 2024-07-25 15:46:45 -04:00
Lauren Tan
9f3bbb05ab [compiler] Make inserting outlined functions more resilient
To handle more cases, always append the synthetic outlined function as a
new child of the module rather than make assumptions about the original
function. This should handle whatever case where the original function
expression may be a child of a variety of parents

ghstack-source-id: 8581edb8be
Pull Request resolved: https://github.com/facebook/react/pull/30466
2024-07-25 15:38:19 -04:00
Lauren Tan
d529a43212 [compiler] Add repro for outlining in non VarDecls
ghstack-source-id: 7688987b02
Pull Request resolved: https://github.com/facebook/react/pull/30465
2024-07-25 15:38:19 -04:00
Lauren Tan
88bf4e197a [compiler] Always emit FuncDecl for outlined functions
Addresses follow up feedback from #30446. Since the outlined function is
guaranteed to have a module-scoped unique identifier name, we can
simplify the insertion logic for the outlined function to always emit a
function declaration rather than switch based on the original function
type. This is fine because the outlined function is synthetic anyway.

ghstack-source-id: 0a4d1f7b0a
Pull Request resolved: https://github.com/facebook/react/pull/30464
2024-07-25 15:38:18 -04:00
Sathya Gunsasekaran
c5fa460784 [compiler] Add tests for incorrect global mutation detection
If a function expression that mutates a global is passed as a prop,
we don't throw an error as we assume it's not called in render.

But if this function expression is captured in an object and passed down
as prop, we throw an error.

ghstack-source-id: 74cacee09f565550007b2e01fa8877ad64ccfbe9
Pull Request resolved: https://github.com/facebook/react/pull/30456
2024-07-25 17:47:20 +01:00
Sebastian Markbåge
e8df0cf9f7 Switch to binding the console with badging instead of calling it directly (#30461)
This is a major nit but this avoids an extra stack frame when we're
replaying logs.

Normally the `printToConsole` frame doesn't show up because it'd be
ignore listed.

<img width="421" alt="Screenshot 2024-07-25 at 11 49 39 AM"
src="https://github.com/user-attachments/assets/81334c2f-e19e-476a-871e-c4db9dee294e">

When you expand to show ignore listed frames a ton of other frames show
up.

<img width="516" alt="Screenshot 2024-07-25 at 11 49 47 AM"
src="https://github.com/user-attachments/assets/2ab8bdfb-464c-408d-9176-ee2fabc114b6">

The annoying thing about this frame is that it's at the top of the stack
where as typically framework stuff ends up at the bottom and something
you can ignore. The user space stack comes first.

With this fix there's no longer any `printToConsole` frame.

<img width="590" alt="Screenshot 2024-07-25 at 12 09 09 PM"
src="https://github.com/user-attachments/assets/b8365d53-31f3-43df-abce-172d608d3c9c">

Am I wiling to eat the added complexity and slightly slower performance
for this nit? Definitely.
2024-07-25 12:32:16 -04:00
Lauren Tan
66968535c6 Bump version to 0.0.0-experimental-176c31a-20240725 2024-07-25 12:27:06 -04:00
Lauren Tan
9f04494b8e Bump version to 0.0.0-experimental-47e15aa-20240725 2024-07-25 12:27:05 -04:00
Lauren Tan
2e800a81d3 Bump version to 0.0.0-experimental-f1f288c-20240725 2024-07-25 12:27:04 -04:00
Jack Pope
14a4699ff1 Remove allowConcurrentByDefault flag (#30445)
Following https://github.com/facebook/react/pull/30436

Concurrent by default strategy has been unshipped. Here we clean up the
`allowConcurrentByDefault` path and related logic/tests.

For now, this keeps the `concurrentUpdatesByDefaultOverride` argument in
`createContainer` and `createHydrationContainer` and ignores the value
to prevent more breaking changes to `react-reconciler` in the RC stage.
2024-07-25 11:59:50 -04:00
Sebastian Markbåge
c2d103594d Configure the requested environment and annotate tasks at boundary between environments (#30455)
This enables configuring the name of the requested environment.

When we currently use createTask, we start with a `"use server"`
annotation. This option basically configures that string.

I now also deal with the case when switching environments along the
owner path. If you go from `"Third Party"` to `"Server"` to `"Client"`,
it'll have a task named `"use third party"` at the root, then `"use
server"` and then finally `"use client"`.

We don't really have the concept of a Server Component making a request
during render to then create another Server Component. Really the inner
one should conceptually have the first one as its owner in that case. So
currently the inner one will always have a null owner. We could somehow
connect them in this server-to-server case.

We don't currently have a way to configure the `"use client"` option but
I figured maybe that could be inferred by the server environment that
the Flight Client is executed within.

Note: We did talk before about annotating each stack frame with the
environment. You can effectively do that manually when parsing
`rsc://React/{environment}/` from `captureOwnerStack`. However, we can't
do that natively. At least not without deeper integration. Because it's
the source map that's responsible for the actual function name of each
stack frame - not what we give it at runtime. So for the native stacks,
the task showing the change in environment is more practical.
2024-07-25 11:46:58 -04:00
Sebastian Markbåge
e5d22459ff [Flight] Include environment name both in the virtual URL and findSourceMapURL (#30452)
This way you can use the environment to know where to look for the
source map in case you have multiple server environments.

This becomes part of the public protocol since it's part of what you'll
parse out of the `rsc://React/` prefixed URLs inside of
`captureOwnerStack`.
2024-07-25 11:14:24 -04:00
Sebastian Markbåge
4b62400765 [Flight] Add filterStackFrame options to the RSC Server (#30447)
This lets you customize the filter, for example allowing node_modules or
filter out additional functions that you don't want to include when
sending the stack to the client.

Notably this doesn't filter out Server Components out of the parent
stack. Those are just like a view of the tree by name. Not virtual stack
frames.
2024-07-25 10:50:56 -04:00
Jack Pope
e4b4aac2a0 Fix existing usage of names/type in build command (#30450)
https://github.com/facebook/react/pull/30422 broke existing build
shortcuts.

Revert the usage of `names` (`_`) and `type` args.

`yarn build-for-devtools` / `yarn build-for-devtools-dev` / `yarn
build-for-devtools-prod` should all work again.

Moved the bundleType documentation into description so they can be fuzzy
matched. But a build like `yarn build --type FB_WWW_PROD` still works
when matched exactly.

There's probably a better way to document the positional `names` arg in
the `--help` command, but didn't see it when browsing the yargs docs so
let's just fix the existing builds for now.

Now:

```
% yarn build --help
yarn run v1.22.19
$ node ./scripts/rollup/build-all-release-channels.js --help
Options:
  --help                Show help                                                                                                                                             [boolean]
  --version             Show version number                                                                                                                                   [boolean]
  --releaseChannel, -r  Build the given release channel.                                                                                   [string] [choices: "experimental", "stable"]
  --index, -i           Worker id.                                                                                                                                             [number]
  --total, -t           Total number of workers.                                                                                                                               [number]
  --ci                  Run tests in CI                                                                                                                 [choices: "circleci", "github"]
  --type                Build the given bundle type. (NODE_ES2015,ESM_DEV,ESM_PROD,NODE_DEV,NODE_PROD,NODE_PROFILING,BUN_DEV,BUN_PROD,FB_WWW_DEV,FB_WWW_PROD,FB_WWW_PROFILING,RN_OSS_DE
                        V,RN_OSS_PROD,RN_OSS_PROFILING,RN_FB_DEV,RN_FB_PROD,RN_FB_PROFILING,BROWSER_SCRIPT)                                                                    [string]
  --pretty              Force pretty output.                                                                                                                                  [boolean]
  --sync-fbsource       Include to sync build to fbsource.                                                                                                                     [string]
  --sync-www            Include to sync build to www.                                                                                                                          [string]
  --unsafe-partial      Do not clean ./build first.     
```
2024-07-25 07:44:57 -04:00
Hendrik Liebau
76002254b7 Fix resolving of references to deduped props in lazy elements (#30441)
When a model references a deduped object of a blocked element that has
subsequently been turned into a lazy element, we need to wait for the
lazy element's chunk to resolve before resolving the reference.

Without the fix, the new test failed with the following runtime error:

```
TypeError: Cannot read properties of undefined (reading 'children')
  1003 |       let value = chunk.value;
  1004 |       for (let i = 1; i < path.length; i++) {
> 1005 |         value = value[path[i]];
       |                          ^
  1006 |       }
  1007 |       const chunkValue = map(response, value);
  1008 |       if (__DEV__ && chunk._debugInfo) {

  at getOutlinedModel (packages/react-client/src/ReactFlightClient.js:1005:26)
```

The bug was uncovered after updating React in Next.js in
https://github.com/vercel/next.js/pull/66711.
2024-07-24 19:34:41 -04:00
Sebastian Markbåge
933b737f64 Use brackets instead of parenthesis in empty name eval (#30449)
Otherwise V8 will parse it as a URL and mess it up. The bracket is a
magic string meaning empty.
2024-07-24 17:45:22 -04:00
Lauren Tan
a6b7e438ca [compiler] Correctly insert (Arrow)FunctionExpressions
Previously we would insert new (Arrow)FunctionExpressions as a sibling
of the original function. However this would break in the outlining case
as it would cause the original function expression's parent to become a
SequenceExpression, breaking a bunch of assumptions in the babel plugin.

To get around this, we synthesize a new VariableDeclaration to contain
the newly inserted function expression and therefore insert it as a true
sibling to the original function.

Yeah, it's kinda gross

ghstack-source-id: df13e3b439962b95af4bbd82ef4302624668faf7
Pull Request resolved: https://github.com/facebook/react/pull/30446
2024-07-24 16:02:29 -04:00
Lauren Tan
91e4f0712e [compiler] Repro for outlining bug in funcexprs
I discovered this compiler crash while trying to do an internal sync of
the compiler. Any kind of outlining appears to crash the babel plugin
when the component is a function expression.

ghstack-source-id: 4f717674af91d4d4b730e64cbd7a144b9faab13e
Pull Request resolved: https://github.com/facebook/react/pull/30443
2024-07-24 16:02:28 -04:00
Sebastian Silbermann
c0b76a6831 [Flight] Allow parens in filenames when parsing stackframes (#30396) 2024-07-24 20:02:20 +02:00
Sebastian Markbåge
5b37af7daa Stop filtering owner stacks (#30438)
We still filter them before passing from server to client in Flight
Server but when presenting a native stack, we don't need to filter them.
That's left to ignore listing in the presentation.

The stacks are pretty clean regardless thanks to the bottom stack
frames.

We can also unify the owner stack formatters into one shared module
since Fizz/Flight/Fiber all do the same thing. DevTools currently does
the same thing but is forked so it can support multiple versions.
2024-07-24 13:01:07 -04:00
Jan Kassens
ab2135c708 [BE] enable prettier for flow fixtures (#30426)
Since switching to `hermes-parser`, we can parse all flow syntax and no
longer need to exclude these fixtures from prettier.
2024-07-24 10:59:40 -04:00
Jack Pope
e902c45caf Remove forceConcurrentByDefaultForTesting flag (#30436)
Concurrent by default has been unshipped! Let's clean it up.

Here we remove `forceConcurrentByDefaultForTesting`, which allows us to
run tests against both concurrent strategies. In the next PR, we'll
remove the actual concurrent by default code path.
2024-07-24 10:17:33 -04:00
Joe Savona
b943feba35 [compiler] Stop relying on identifier mutable ranges after constructing scopes
Addresses discussion at https://github.com/facebook/react/pull/30399#discussion_r1684693021. Once we've constructed scopes it's invalid to use identifier mutable ranges. The only places we can do this which i can find are ValidateMemoizedEffectDeps (which is already flawed and disabled by default) and ValidatePreservedManualMemoization. I added a todo to the former, and fixed up the latter.

The idea of the fix is that for StartMemo dependencies, if they needed to be memoized (identifier.scope != null) then that scope should exist and should have already completed. If they didn't need a scope or can't have one created (eg their range spans a hook), then their scope would be pruned. So if the scope is set, not pruned, and not completed, then it's an error.

For declarations (FinishMemo) the existing logic applies unchanged.

ghstack-source-id: af5bfd88553de3e30621695f9d139c4dc5efb997
Pull Request resolved: https://github.com/facebook/react/pull/30428
2024-07-24 09:22:16 +09:00
Joe Savona
c08b516007 [compiler] repro of false positive for ValidatePreserveManualMemo
ghstack-source-id: 235c5bae0828b79db758871006e4c091923ca5fd
Pull Request resolved: https://github.com/facebook/react/pull/30431
2024-07-24 09:22:16 +09:00
Joe Savona
f987f1a057 [compiler] Maintain RPO and unique instruction ids when constructing scope terminals
Later passes may rely on HIR invariants such as blocks being in RPO or instructions having unique, ascending InstructionIds. However, BuildReactiveScopeTerminalsHIR doesn't currently gurantee this.

This PR updates that pass to first restore RPO, fixup predecessors (the previous logic tried to do this but failed on unreachable blocks, where `markPredecessors()` handles that case), and renumber instructions. Then it walks instructions and scopes to update identifier and scope ranges given the new instruction ids.

ghstack-source-id: 2a99df02ac9d125b202cae369e2dc4dccefb0625
Pull Request resolved: https://github.com/facebook/react/pull/30399
2024-07-24 09:22:16 +09:00
Joe Savona
ca5fef0f4a [compiler] Flatten scopes based on fallthrough, not scope range
Once we create scopes, we should prefer to use the block structure to identify active scope ranges rather than the scope range. They _should_ always be in sync, but ultimately the block structure determine the active range (ie the id of the 'scope' terminal and the terminal's fallthrough block).

ghstack-source-id: 730b6d1cfaf0eb689d71057c78a48045ac4fb11c
Pull Request resolved: https://github.com/facebook/react/pull/30398
2024-07-24 09:22:16 +09:00
Joe Savona
7c7087811e [compiler] printTerminal always prints instruction id
Doing some debugging I noticed that a few of the newer terminals kinds weren't printing the instruction id.

ghstack-source-id: e0e4c96aeefdfe09d3be1527fd7103b4e506eb8e
Pull Request resolved: https://github.com/facebook/react/pull/30397
2024-07-24 09:22:16 +09:00
Sebastian Markbåge
da4abf0047 [Fiber] Call life-cycles with a react-stack-bottom-frame stack frame (#30429)
Stacked on #30427.

Most hooks and such are called inside renders which already have these
on the stack but life-cycles that call out on them are useful to cut off
too.

Typically we don't create JSX in here so they wouldn't be part of owner
stacks anyway but they can be apart of plain stacks such as the ones
prefixes to console logs or printed by error dialogs.

This lets us cut off any React internals below. This should really be
possible using just ignore listing too ideally.

At this point we should maybe just build a Babel plugin that lets us
annotate a function to need to have this name.
2024-07-23 18:49:42 -04:00
Sebastian Markbåge
e2cac67533 [Flight] Prefix owner stacks added to the console.log with the current stack (#30427)
The current stack is available in the native UI but that's hidden by
default so you don't see the actual current component on the stack.

This is unlike the native async stacks UI where they're all together.

So we prefix the stack with the current stack first.

<img width="279" alt="Screenshot 2024-07-22 at 10 05 13 PM"
src="https://github.com/user-attachments/assets/8f568fda-6493-416d-a0be-661caf44d808">

---------

Co-authored-by: Ruslan Lesiutin <rdlesyutin@gmail.com>
2024-07-23 16:48:32 -04:00
Lauren Tan
942eb80381 [compiler] Repro for interaction b/w outlining and idx
babel-plugin-idx enforces that its 2nd argument is an arrow function, so
outlining needs to skip over lambdas that are args to idx. This PR adds
a small repro highlighting the issue.

ghstack-source-id: b4627ec552056f33090e2f7bc0536a6006d79d18
Pull Request resolved: https://github.com/facebook/react/pull/30435
2024-07-23 15:56:56 -04:00
Lauren Tan
08b4ee83cc [compiler] Add babel-plugin-idx to snap
To surface any potential conflicts with this plugin, let's install it
into snap so we can surface any runtime errors after compilation

ghstack-source-id: 545eee6fb7f6401e919422581cf64070da581d50
Pull Request resolved: https://github.com/facebook/react/pull/30434
2024-07-23 15:56:56 -04:00
Sebastian Markbåge
fc74a3a3e6 Transform JSX to Lazy Requires instead of Wrappers (#30433)
This ensures that we can keep overriding what runtime to use by
resetting modules while still using the automatic JSX plugin. This is
like the "inline requires" transform but just for JSX.

I got sick of trying to figure out workarounds to hide the extra stack
frame that appears due to the wrappers.
2024-07-23 15:51:23 -04:00
Mofei Zhang
b34b750729 [compiler][eslint] remove compilationMode override; report bailouts on first line
ghstack-source-id: 1870c7aa09
Pull Request resolved: https://github.com/facebook/react/pull/30423
2024-07-23 14:24:36 -04:00
Mofei Zhang
50640a1b88 [compiler][repro] Test fixture for fbt whitespace bug
ghstack-source-id: 749feb2cb8
Pull Request resolved: https://github.com/facebook/react/pull/30394
2024-07-23 14:24:36 -04:00
Tom Sherman
9cc0f6e68d Log Fragment name when trying to render a lazy fragment (#30372) 2024-07-23 19:00:51 +02:00
Jack Pope
f6cce072cf Update yarn build options (#30422)
Improve command documentation and make it easier to build specific
bundle types

**Before**
```
% yarn build --help              
yarn run v1.22.19
$ node ./scripts/rollup/build-all-release-channels.js --help
Options:
  --help                Show help                                                                                   [boolean]
  --version             Show version number                                                                         [boolean]
  --releaseChannel, -r  Build the given release channel.                         [string] [choices: "experimental", "stable"]
  --index, -i           Worker id.                                                                                   [number]
  --total, -t           Total number of workers.                                                                     [number]
  --ci                  Run tests in CI                                                       [choices: "circleci", "github"]
  Done in 0.69s.
```

**After**
```
% yarn build --help
yarn run v1.22.19
$ node ./scripts/rollup/build-all-release-channels.js --help
Options:
  --help                Show help                                                                                                               [boolean]
  --version             Show version number                                                                                                     [boolean]
  --releaseChannel, -r  Build the given release channel.                                                     [string] [choices: "experimental", "stable"]
  --index, -i           Worker id.                                                                                                               [number]
  --total, -t           Total number of workers.                                                                                                 [number]
  --bundle              Build the given bundle type.
           [choices: "NODE_ES2015", "ESM_DEV", "ESM_PROD", "NODE_DEV", "NODE_PROD", "NODE_PROFILING", "BUN_DEV", "BUN_PROD", "FB_WWW_DEV", "FB_WWW_PROD",
                     "FB_WWW_PROFILING", "RN_OSS_DEV", "RN_OSS_PROD", "RN_OSS_PROFILING", "RN_FB_DEV", "RN_FB_PROD", "RN_FB_PROFILING", "BROWSER_SCRIPT"]
  --ci                  Run tests in CI                                                                                   [choices: "circleci", "github"]
  --names               Build for matched bundle names. Example: "react-test,index.js".                                                           [array]
  --pretty              Force pretty output.                                                                                                    [boolean]
  --sync-fbsource       Include to sync build to fbsource.                                                                                       [string]
  --sync-www            Include to sync build to www.                                                                                            [string]
  --unsafe-partial      Do not clean ./build first.                                                                                             [boolean]
  Done in 0.61s.
```

Changes
- Use yargs to document existing options: `pretty`, `sync-fbsource`,
`sync-www`, `unsafe-partial`.
- Move `_` arg to `names` option for consistency with other options and
discoverability through yargs help
- Add `bundle` option in place of `argv.type` that allows choices of any
BundleType to be passed in directly.
2024-07-23 11:27:51 -04:00
Jan Kassens
70484844bf [BE] switch to hermes parser for prettier (#30421)
This will allow us to parse new flow syntax since the `flow` parser is
no longer updated.

I had to exclude some files and have them fall back to `flow` parser
since they contain invalid graphql syntax that makes the plugin crash.
2024-07-22 19:16:13 -04:00
Sebastian Markbåge
f83615ad30 Reverse engineer original stack frames when virtual frames are re-serialized (#30416)
Stacked on #30410.

If we've parsed another RSC stream on the server from a different RSC
server, while using `findSourceMapURL`, the Flight Client ends up adding
a `rsc://React/` prefix and a numeric suffix to the URL. It's a virtual
file that represents the virtual eval:ed frame in that environment.

If we then see that same stack again, we'd serialize a virtual frame to
another virtual. Meaning `findSourceMapURL` on the client would see the
virtual frame of the intermediate server and it would have to strip it
to figure out what source map to use.

This PR strips it in the Server if we see a virtual frame. At each new
client it always refers to the original stack.

We don't have to do this. We could leave it to each `findSourceMapURL`
implementation and `captureOwnerStack` parser to recursively strip each
layer. It could maybe be useful to have the environment name in the
virtual frame to know which server to look for the source map in.
2024-07-22 18:50:14 -04:00
Sebastian Markbåge
43dac1ee8d [DevTools] Implement Owner Stacks (#30417)
Stacked on #30410.

Use "owner stacks" as the appended component stack if it is available on
the Fiber. This will only be available if the enableOwnerStacks flag is
on. Otherwise it fallback to parent stacks. In prod, there's no owner so
it's never added there.

I was going back and forth on whether to inject essentially
`captureOwnerStack` as part of the DevTools hooks or replicate the
implementation but decided to replicate the implementation.

The DevTools needs all the same information from internals to implement
owner views elsewhere in the UI anyway so we're not saving anything in
terms of the scope of internals. Additionally, we really need this
information for non-current components as well like "rendered by" views
of the currently selected component.

It can also be useful if we need to change the format after the fact
like we did for parent stacks in:
https://github.com/facebook/react/pull/30289

Injecting the implementation would lock us into specifics both in terms
of what the core needs to provide and what the DevTools can use.

The implementation depends on the technique used in #30369 which tags
frames to strip out with `react-stack-bottom-frame`. That's how the
implementation knows how to materialize the error if it hasn't already.

Firefox:

<img width="487" alt="Screenshot 2024-07-21 at 11 33 37 PM"
src="https://github.com/user-attachments/assets/d3539b53-4578-4fdd-af25-25698b2bcc7d">

Follow up: One thing about this view is that it doesn't include the
current actual synchronous stack. When I used to append these I would
include both the real current stack and the owner stack. That's because
the owner stack doesn't include the name of the currently executing
component. I'll probably inject the current stack too in addition to the
owner stack. This is similar to how native Async Stacks are basically
just appended onto the current stack rather than its own.
2024-07-22 18:49:44 -04:00
Jan Kassens
b7e7f1a3fa [BE] upgrade prettier to 3.3.3 (#30420)
Mostly just changes in ternary formatting.
2024-07-22 16:09:01 -04:00
Jan Kassens
d7c4334ce9 Add _debugStack and _debugTask under enableObjectFiber flag (#30337) 2024-07-22 14:10:08 -04:00
Jan Kassens
d025ddd3b9 Set enableFastJSX flag to true (#30343)
When these to diffs are landed, we can merge this

- [x] D59772879
- [x] D59773043
2024-07-22 11:50:35 -04:00
Sebastian Markbåge
06763852de [Flight] Parse Stack on the Server and Transfer Structured Stack (#30410)
Stacked on #30401.

Previously we were transferring the original V8 stack trace string to
the client and then parsing it there. However, really the server is the
one that knows what format it is and it should be able to vary by server
environment.

We also don't use the raw string anymore (at least not in
enableOwnerStacks). We always create the native Error stacks.

The string also made it unclear which environment it is and it was
tempting to just use it as is.

Instead I parse it on the server and make it a structured stack in the
transfer format. It also makes it clear that it needs to be formatted in
the current environment before presented.
2024-07-22 11:18:55 -04:00
Sebastian Markbåge
b15c1983dc [Flight] Normalize Stack Using Fake Evals (#30401)
Stacked on https://github.com/facebook/react/pull/30400 and
https://github.com/facebook/react/pull/30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
https://github.com/facebook/react/pull/30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.
2024-07-22 11:03:15 -04:00
Sebastian Markbåge
792f192114 Tag all user space call sites with the "react-stack-bottom-frame" name (#30369)
Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.
2024-07-22 10:47:38 -04:00
Sebastian Markbåge
f603426f91 [Flight] Consistently flag debugOwner field (#30400)
This is available outside `enableOwnerStack` but we weren't consistently
using it and so it has different hidden classes.
2024-07-19 21:13:03 -04:00
Sebastian Silbermann
fac78affb0 [test] Add findSourceMapURL to ReactNoopFlightClient (#30395) 2024-07-19 22:45:27 +02:00
Lauren Tan
66df94460e [ci] Update sizebot node_modules caching strategy
The download job for sizebot requires both modules from the root repo
but also has a nested yarn lockfile in scripts/release. Calculate the
hash for the cache using both lockfiles.

ghstack-source-id: fc1703b547ab906ee244cfa3540414a6df8c660e
Pull Request resolved: https://github.com/facebook/react/pull/30393
2024-07-18 17:42:46 -04:00
Lauren Tan
d439f279ff Add run prettier commit to .git-blame-ignore-revs
ghstack-source-id: a10c3b3d6412873023747d6f253f8793fa689b60
Pull Request resolved: https://github.com/facebook/react/pull/30391
2024-07-18 17:42:45 -04:00
Lauren Tan
abc0b712aa [ci] Fix sizebot job omitting build_and_lint dependency
ghstack-source-id: 3d457818c9812e320ddbd2bb3f35d5b2e3b92bdc
Pull Request resolved: https://github.com/facebook/react/pull/30392
2024-07-18 17:42:45 -04:00
Lauren Tan
3f4391d589 [ci] Only run sizebot on pull requests
Oops, forgot to port this bit over.

ghstack-source-id: 7fae8021d80a2ea58e89759147553e6910b49a0f
Pull Request resolved: https://github.com/facebook/react/pull/30390
2024-07-18 17:42:45 -04:00
Jan Kassens
fd2b3e13d3 Compiler: unfork prettier config (#30205)
Updates the prettier config to format all `.ts` and `.tsx` files in the
repo using the existing defaults and removing overrides.

The first commit in this PR contains the config changes, the second is
just the result of running `yarn prettier-all`.
2024-07-18 17:00:24 -04:00
Lauren Tan
6fac743ed7 [circleci] Inline scrape_warning_messages
This job was only being used for process_artifacts_combined in circleci
but as it was a separate job would incur the cost and time of spinning
up a new CI worker. We can simply inline this into
process_artifacts_combined.

Test plan: Download
[build.tgz](https://app.circleci.com/pipelines/github/facebook/react/57183/workflows/3b22a99e-b8a5-4c80-9682-576f24581b00/jobs/956476/artifacts)
from circleci and observe that the __test_utils__ directory is still
present.

ghstack-source-id: 8748d8fb9cf01d8fffe7e7590729a44def265c79
Pull Request resolved: https://github.com/facebook/react/pull/30388
2024-07-18 16:41:22 -04:00
Lauren Tan
7af570c3d9 [ci] Remove sizebot from circleci
Now that the job is migrated to GH, we can remove this from circleci.
Note that sizebot still comments on this PR.

ghstack-source-id: 337661e3b00d3d3686a539bf0877d8526b0f15b9
Pull Request resolved: https://github.com/facebook/react/pull/30387
2024-07-18 16:41:22 -04:00
Lauren Tan
4fb11acd79 [ci] Add sizebot to gh actions
Wires up sizebot in gh actions. I also fixed sizebot incorrectly
reporting that ReactAllWarnings was deleted.

ghstack-source-id: d3b1bb2088651409e8656c66f1eb28ab534a0604
Pull Request resolved: https://github.com/facebook/react/pull/30380
2024-07-18 16:41:22 -04:00
Lauren Tan
f6bc05a22a [ci] Add script to download artifact from github
Updates the forked script to download build artifacts from GitHub. Note
that this PR just adds the script, it does not add it to GH actions yet.

ghstack-source-id: 08b0d2f93a88d55386e43a7d1bf88a67a117e899
Pull Request resolved: https://github.com/facebook/react/pull/30376
2024-07-18 16:41:21 -04:00
Lauren Tan
eed9ca203c [ci] Use yargs to parse args to download-experimental-build
ghstack-source-id: 0b222cf61368f1cafdd7b44cdb1a574228cbe403
Pull Request resolved: https://github.com/facebook/react/pull/30375
2024-07-18 16:41:21 -04:00
Lauren Tan
21be03bfed [ci] Fork scripts/release/download-experimental-build.js
This PR just copies the original file without any modifications for
easier review.

The original script makes assumptions about CircleCI that are difficult
to untangle. So let's fork this file temporarily for GitHub actions.
Later on we will remove the original and rename this fork back to the
original naming.

ghstack-source-id: 0ce078538e76349d9ecb69ffc1c352e24390c426
Pull Request resolved: https://github.com/facebook/react/pull/30374
2024-07-18 16:41:21 -04:00
Lauren Tan
76f8cf5277 [ci] Rename temporary build parts
Adds an _ prefix to temporary build parts from parallelization to allow
easier merging in later passes.

ghstack-source-id: 714da85972e138d389224f67601dc7aaa6676e11
Pull Request resolved: https://github.com/facebook/react/pull/30385
2024-07-18 16:41:20 -04:00
2229 changed files with 22259 additions and 19557 deletions

View File

@@ -1,395 +0,0 @@
version: 2.1
aliases:
- &docker
- image: cimg/node:18.20.1-browsers
- &environment
TZ: /usr/share/zoneinfo/America/Los_Angeles
- &TEST_PARALLELISM 20
- &attach_workspace
at: build
commands:
setup_node_modules:
description: "Restore node_modules"
steps:
- restore_cache:
name: Restore yarn cache
keys:
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
- run:
name: Install dependencies
command: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
environment:
# If we start needing the Electron binary, please ensure the binary is cached in CI following https://www.electronjs.org/docs/latest/tutorial/installation
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
- save_cache:
name: Save yarn cache
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
# The CircleCI API doesn't yet support triggering a specific workflow, but it
# does support triggering a pipeline. So as a workaround you can triggger the
# entire pipeline and use parameters to disable everything except the workflow
# you want. CircleCI recommends this workaround here:
# https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2-
parameters:
# This is only set when triggering the CI pipeline via an API request.
prerelease_commit_sha:
type: string
default: ''
jobs:
scrape_warning_messages:
docker: *docker
environment: *environment
steps:
- checkout
- setup_node_modules
- run:
command: |
mkdir -p ./build/__test_utils__
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- persist_to_workspace:
root: .
paths:
- build
yarn_build:
docker: *docker
environment: *environment
parallelism: 40
steps:
- checkout
- setup_node_modules
- run: yarn build --ci=circleci
- persist_to_workspace:
root: .
paths:
- build
download_build:
docker: *docker
environment: *environment
parameters:
revision:
type: string
steps:
- checkout
- setup_node_modules
- run:
name: Download artifacts for revision
command: |
git fetch origin main
cd ./scripts/release && yarn && cd ../../
scripts/release/download-experimental-build.js --commit=<< parameters.revision >> --allowBrokenCI
- persist_to_workspace:
root: .
paths:
- build
download_base_build_for_sizebot:
docker: *docker
environment: *environment
steps:
- checkout
- setup_node_modules
- run:
name: Download artifacts for base revision
command: |
git fetch origin main
cd ./scripts/release && yarn && cd ../../
scripts/release/download-experimental-build.js --commit=$(git merge-base HEAD origin/main) --allowBrokenCI
mv ./build ./base-build
- run:
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
name: Delete extraneous files
command: rm -rf ./base-build/node_modules
- persist_to_workspace:
root: .
paths:
- base-build
process_artifacts_combined:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
# Compress build directory into a single tarball for easy download
- run: tar -zcvf ./build.tgz ./build
# TODO: Migrate scripts to use `build` directory instead of `build2`
- run: cp ./build.tgz ./build2.tgz
- store_artifacts:
path: ./build2.tgz
- store_artifacts:
path: ./build.tgz
sizebot:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
- setup_node_modules
- run:
command: node ./scripts/tasks/danger
- store_artifacts:
path: sizebot-message.md
build_devtools_and_process_artifacts:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run:
environment:
RELEASE_CHANNEL: experimental
command: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
- store_artifacts:
path: ./build/devtools.tgz
# Simplifies getting the extension for local testing
- store_artifacts:
path: ./build/devtools/chrome-extension.zip
destination: react-devtools-chrome-extension.zip
- store_artifacts:
path: ./build/devtools/firefox-extension.zip
destination: react-devtools-firefox-extension.zip
run_devtools_e2e_tests:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run:
name: Playwright install deps
command: |
npx playwright install
sudo npx playwright install-deps
- run:
environment:
RELEASE_CHANNEL: experimental
command: ./scripts/circleci/run_devtools_e2e_tests.js
run_devtools_tests_for_versions:
docker: *docker
environment: *environment
parallelism: *TEST_PARALLELISM
parameters:
version:
type: string
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >> --replaceBuild
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion << parameters.version >> --ci=circleci
run_devtools_e2e_tests_for_versions:
docker: *docker
environment: *environment
parallelism: *TEST_PARALLELISM
parameters:
version:
type: string
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run:
name: Playwright install deps
command: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >>
- run:
environment:
RELEASE_CHANNEL: experimental
command: ./scripts/circleci/run_devtools_e2e_tests.js << parameters.version >>
- run:
name: Cleanup build regression folder
command: rm -r ./build-regression
- store_artifacts:
path: ./tmp/screenshots
publish_prerelease:
parameters:
commit_sha:
type: string
release_channel:
type: string
dist_tag:
type: string
docker: *docker
environment: *environment
steps:
- checkout
- setup_node_modules
- run:
name: Run publish script
command: |
git fetch origin main
cd ./scripts/release && yarn && cd ../../
scripts/release/prepare-release-from-ci.js --skipTests -r << parameters.release_channel >> --commit=<< parameters.commit_sha >>
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
workflows:
build_and_test:
unless: << pipeline.parameters.prerelease_commit_sha >>
jobs:
- yarn_build:
filters:
branches:
ignore:
- builds/facebook-www
- scrape_warning_messages:
filters:
branches:
ignore:
- builds/facebook-www
- process_artifacts_combined:
requires:
- scrape_warning_messages
- yarn_build
- download_base_build_for_sizebot:
filters:
branches:
ignore:
- main
- builds/facebook-www
- sizebot:
filters:
branches:
ignore:
- main
requires:
- download_base_build_for_sizebot
- yarn_build
devtools_regression_tests:
unless: << pipeline.parameters.prerelease_commit_sha >>
triggers:
- schedule:
# DevTools regression tests run once a day
cron: "0 0 * * *"
filters:
branches:
only:
- main
jobs:
- download_build:
revision: << pipeline.git.revision >>
- build_devtools_and_process_artifacts:
requires:
- download_build
- run_devtools_tests_for_versions:
requires:
- build_devtools_and_process_artifacts
matrix:
parameters:
version:
- "16.0"
- "16.5" # schedule package
- "16.8" # hooks
- "17.0"
- "18.0"
- run_devtools_e2e_tests_for_versions:
requires:
- build_devtools_and_process_artifacts
matrix:
parameters:
version:
- "16.0"
- "16.5" # schedule package
- "16.8" # hooks
- "17.0"
- "18.0"
# Used to publish a prerelease manually via the command line
publish_preleases:
when: << pipeline.parameters.prerelease_commit_sha >>
jobs:
- publish_prerelease:
name: Publish to Canary channel
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
release_channel: stable
# The tags to use when publishing canaries. The main one we should
# always include is "canary" but we can use multiple (e.g. alpha,
# beta, rc). To declare multiple, use a comma-separated string, like
# this:
# dist_tag: "canary,alpha,beta,rc"
#
# TODO: We currently tag canaries with "next" in addition to "canary"
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: "canary,next,rc"
- publish_prerelease:
name: Publish to Experimental channel
requires:
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
# dist tags.
- Publish to Canary channel
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
release_channel: experimental
dist_tag: experimental
# Publishes on a cron schedule
publish_preleases_nightly:
unless: << pipeline.parameters.prerelease_commit_sha >>
triggers:
- schedule:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
cron: "10 16 * * 1,2,3,4,5"
filters:
branches:
only:
- main
jobs:
- publish_prerelease:
name: Publish to Canary channel
commit_sha: << pipeline.git.revision >>
release_channel: stable
dist_tag: "canary,next,rc"
- publish_prerelease:
name: Publish to Experimental channel
requires:
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
# dist tags.
- Publish to Canary channel
commit_sha: << pipeline.git.revision >>
release_channel: experimental
dist_tag: experimental

View File

@@ -490,7 +490,7 @@ module.exports = {
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-shared/src/hook.js',
'packages/react-devtools-shared/src/backend/console.js',
'packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js',
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
],
globals: {
__IS_CHROME__: 'readonly',

View File

@@ -1 +1,2 @@
c998bb1ed4b3285398c9c7797135d3f060243c6a
fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b

View File

@@ -10,6 +10,8 @@ on:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
defaults:
run:
@@ -23,7 +25,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules

View File

@@ -10,6 +10,8 @@ on:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
defaults:
run:
@@ -34,7 +36,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
@@ -53,7 +55,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
@@ -77,7 +79,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules

View File

@@ -1,208 +0,0 @@
name: (DevTools) Check for bug repro
on:
issues:
types: [opened, edited]
issue_comment:
types: [created, edited]
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
check-repro:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const URL_REGEXP = /### Website or app[\r\n]+([^#]+)###/m;
const REPRO_STEPS_REGEXP = /### Repro steps[\r\n]+([^#]+)###/m;
const LABEL_NEEDS_MORE_INFORMATION = "Resolution: Needs More Information";
const LABEL_UNCONFIRMED = "Status: Unconfirmed";
function debug(...args) {
core.info(args.map(JSON.stringify).join(' '));
}
if (context.payload.comment) {
debug('Ignoring comment update.');
return;
}
const user = context.payload.sender.login;
const issue = context.payload.issue;
const body = issue.body;
const urlMatch = body.match(URL_REGEXP);
const reproStepsMatch = body.match(REPRO_STEPS_REGEXP);
const url = urlMatch !== null ? urlMatch[1].trim() : null;
const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null;
if (!url || !reproSteps) {
debug('This issue is not a DevTools bug report.');
return;
}
debug(`found URL "${url}"`);
debug(`found repro steps "${reproSteps}"`);
async function createComment(comment) {
// Format
comment = comment
.split("\n")
.map((line) => line.trim())
.join("\n")
.trim();
await github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment,
});
}
async function getGitHubActionComments() {
debug(`Loading existing comments...`);
const comments = await github.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
return comments.data.filter(comment => {
debug(`comment by user: "${comment.user.login}"`);
return comment.user.login === 'github-actions[bot]';
});
}
async function getIssueLabels() {
const issues = await github.issues.listLabelsOnIssue({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
return issues.data;
}
async function updateIssue(state, assignees = []) {
await github.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
state,
assignees,
});
}
async function closeWithComment(comment) {
if (issue.state !== 'open') {
debug(`Issue is not open`);
return;
}
const labels = await getIssueLabels();
const label = labels.find(label => label.name === LABEL_UNCONFIRMED);
if (!label) {
debug(`Issue was not opened via DevTools bug report template`);
return;
}
const comments = await getGitHubActionComments();
if (comments.length > 0) {
debug(`Already commented on issue; won't comment again`);
return;
}
debug(`Missing required information`);
await github.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [LABEL_NEEDS_MORE_INFORMATION],
});
await createComment(comment);
await updateIssue('closed', [user]);
}
async function openWithComment(comment) {
if (issue.state !== 'closed') {
debug(`Issue is already open`);
return;
}
const labels = await getIssueLabels();
const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION);
if (!label) {
debug(`Issue was not tagged as needs information`);
return;
}
const comments = await getGitHubActionComments();
if (comments.length === 0) {
debug(`Issue was closed by someone else; won't reopen`);
return;
}
debug(`Re-opening closed issue`);
await github.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: LABEL_NEEDS_MORE_INFORMATION,
});
await createComment(comment);
await updateIssue('open');
}
const PROBABLY_NOT_A_URL_REGEX = /(^Chrome$|^Firefox$| Website)/i;
const COMMENT_HEADER = `
@${user}: We're sorry you've seen this error. ❤️
`.trim();
const COMMENT_FOOTER = `
Please help us by providing a link to a CodeSandbox (https://codesandbox.io/s/new), a repository on GitHub, or a minimal code example that reproduces the problem. (Screenshots or videos can also be helpful if they help provide context on how to repro the bug.)
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
Issues without repros are automatically closed but we will re-open if you update with repro info.
`.trim();
if (url.includes("localhost")) {
closeWithComment(`
${COMMENT_HEADER}
Unfortunately the URL you provided ("localhost") is not publicly accessible. (This means that we will not be able to reproduce the problem you're reporting.)
${COMMENT_FOOTER}
`);
} else if (url.length < 10 || url.match(PROBABLY_NOT_A_URL_REGEX)) {
closeWithComment(`
${COMMENT_HEADER}
It looks like you forgot to specify a valid URL. (This means that we will not be able to reproduce the problem you're reporting.)
${COMMENT_FOOTER}
`);
} else if (reproSteps.length < 25) {
closeWithComment(`
${COMMENT_HEADER}
Unfortunately, it doesn't look like this issue has enough info for one of us to reproduce and fix it though.
${COMMENT_FOOTER}
`);
} else {
openWithComment(`
Thank you for providing repro steps! Re-opening issue now for triage.
`);
}

View File

@@ -0,0 +1,173 @@
name: (DevTools) Regression Tests
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
download_build:
name: Download base build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || '$(git rev-parse origin/main)' }}
- name: Display structure of build
run: ls -R build
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build
path: build
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: download_build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
name: build
path: build
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
env:
RELEASE_CHANNEL: experimental
- name: Display structure of build
run: ls -R build
- name: Archive devtools build
uses: actions/upload-artifact@v4
with:
name: react-devtools
path: build/devtools.tgz
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
run_devtools_tests_for_versions:
name: Run DevTools tests for versions
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
strategy:
matrix:
version:
- "16.0"
- "16.5" # schedule package
- "16.8" # hooks
- "17.0"
- "18.0"
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
run: ls -R build
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }} --replaceBuild
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion ${{ matrix.version }} --ci
run_devtools_e2e_tests_for_versions:
name: Run DevTools e2e tests for versions
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
strategy:
matrix:
version:
- "16.0"
- "16.5" # schedule package
- "16.8" # hooks
- "17.0"
- "18.0"
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
run: ls -R build
- name: Playwright install deps
run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
- run: ls -R build-regression
- run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}
env:
RELEASE_CHANNEL: experimental
- name: Cleanup build regression folder
run: rm -r ./build-regression
- uses: actions/upload-artifact@v4
with:
name: screenshots
path: ./tmp/screenshots

View File

@@ -9,6 +9,8 @@ on:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- FLOW -----
@@ -38,7 +40,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -58,7 +60,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -80,7 +82,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -129,7 +131,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -139,7 +141,7 @@ jobs:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci=github --shard=${{ matrix.shard }}
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
@@ -154,7 +156,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
@@ -168,7 +170,7 @@ jobs:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci=github
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -180,9 +182,8 @@ jobs:
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: |
build
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: build
test_build:
name: yarn test-build
@@ -229,7 +230,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -242,11 +243,12 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci=github
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
process_artifacts_combined:
name: Process artifacts combined
@@ -256,7 +258,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -269,6 +271,7 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
@@ -298,7 +301,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -311,6 +314,7 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
@@ -328,7 +332,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -341,6 +345,7 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
@@ -355,7 +360,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -370,6 +375,7 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
@@ -391,7 +397,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
# Fixture copies some built packages from the workroot after install.
@@ -407,6 +413,7 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Display structure of build
@@ -449,7 +456,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -462,9 +469,10 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- run: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
env:
RELEASE_CHANNEL: experimental
- name: Display structure of build
@@ -494,7 +502,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -507,11 +515,97 @@ jobs:
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/circleci/run_devtools_e2e_tests.js
- run: ./scripts/ci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental
# ----- SIZEBOT -----
download_base_build_for_sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' }}
name: Download base build for sizebot
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
git fetch origin main
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main)
mv ./build ./base-build
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
- name: Delete extraneous files
run: rm -rf ./base-build/node_modules
- name: Display structure of base-build
run: ls -R base-build
- name: Archive base-build
uses: actions/upload-artifact@v4
with:
name: base-build
path: base-build
sizebot:
name: Run sizebot
needs: [build_and_lint, download_base_build_for_sizebot]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- name: Restore archived base-build from origin/main
uses: actions/download-artifact@v4
with:
name: base-build
path: base-build
- name: Display structure of base-build from origin/main
run: ls -R base-build
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4
with:
name: sizebot-message
path: sizebot-message.md

View File

@@ -1,11 +1,21 @@
name: (Runtime) Commit Artifacts for Meta WWW and fbsource
name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2
on:
push:
branches: [main, meta-www, meta-fbsource]
workflow_run:
workflows: ["(Runtime) Build and Test"]
types: [completed]
branches:
- main
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
download_artifacts:
@@ -49,102 +59,27 @@ jobs:
run: |
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
- name: Download and unzip artifacts
uses: actions/github-script@v6
env:
CIRCLECI_TOKEN: ${{secrets.CIRCLECI_TOKEN_DIFFTRAIN}}
- uses: actions/setup-node@v4
with:
script: |
// TODO: Move this to a script file.
const cp = require('child_process');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function execHelper(command, options, streamStdout = false) {
return new Promise((resolve, reject) => {
const proc = cp.exec(
command,
options,
(error, stdout) => (error ? reject(error) : resolve(stdout.trim())),
);
if (streamStdout) {
proc.stdout.pipe(process.stdout);
}
});
}
let artifactsUrl = null;
// This is a temporary, dirty hack to avoid needing a GitHub auth token in the circleci
// workflow to notify this GitHub action. Sorry!
let iter = 0;
spinloop: while (iter < 15) {
const res = await github.rest.repos.listCommitStatusesForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
});
for (const status of res.data) {
if (/process_artifacts_combined/.test(status.context)) {
switch (status.state) {
case 'pending': {
console.log(`${status.context} is still pending`);
break;
}
case 'failure':
case 'error': {
throw new Error(`${status.context} has failed or errored`);
}
case 'success': {
// The status does not include a build ID, but we can extract it
// from the URL. I couldn't find a better way to do this.
const ciBuildId = /\/facebook\/react\/([0-9]+)/.exec(
status.target_url,
)[1];
if (Number.parseInt(ciBuildId, 10) + '' === ciBuildId) {
artifactsUrl =
`https://circleci.com/api/v1.1/project/github/facebook/react/${ciBuildId}/artifacts`;
console.log(`Found artifactsUrl: ${artifactsUrl}`);
break spinloop;
} else {
throw new Error(`${ciBuildId} isn't a number`);
}
break;
}
default: {
throw new Error(`Unhandled status state: ${status.state}`);
break;
}
}
}
}
iter++;
console.log("Sleeping for 60s...");
await sleep(60_000);
}
if (artifactsUrl != null) {
const {CIRCLECI_TOKEN} = process.env;
const res = await fetch(artifactsUrl, {
headers: {
'Circle-Token': CIRCLECI_TOKEN
}
});
const data = await res.json();
if (!Array.isArray(data) && data.message != null) {
throw `CircleCI returned: ${data.message}`;
}
for (const artifact of data) {
if (artifact.path === 'build.tgz') {
console.log(`Downloading and unzipping ${artifact.url}`);
await execHelper(
`curl -L ${artifact.url} -H "Circle-Token: ${CIRCLECI_TOKEN}" | tar -xvz`
);
}
}
} else {
process.exitCode = 1;
}
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha }}
- name: Display structure of build
run: ls -R build
- name: Strip @license from eslint plugin and react-refresh
run: |
sed -i -e 's/ @license React*//' \
@@ -199,9 +134,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
echo ${{ github.event.workflow_run.head_sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
@@ -214,11 +149,11 @@ jobs:
echo "current_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
echo "current_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
echo "current_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: compiled
path: compiled/
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: compiled-rn
path: compiled-rn/
@@ -233,7 +168,7 @@ jobs:
ref: builds/facebook-www
- name: Ensure clean directory
run: rm -rf compiled
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: compiled
path: compiled/
@@ -298,12 +233,12 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: |
${{ github.event.head_commit.message }}
${{ github.event.workflow_run.head_commit.message }}
DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }})
DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.actor }}
commit_user_email: ${{ github.actor }}@users.noreply.github.com
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
create_branch: true
commit_fbsource_artifacts:
@@ -316,7 +251,7 @@ jobs:
ref: builds/facebook-fbsource
- name: Ensure clean directory
run: rm -rf compiled-rn
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: compiled-rn
path: compiled-rn/
@@ -365,7 +300,7 @@ jobs:
git add .
- name: Signing files
if: steps.check_should_commit.outputs.should_commit == 'true'
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
// TODO: Move this to a script file.
@@ -456,10 +391,10 @@ jobs:
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: |
${{ github.event.head_commit.message }}
${{ github.event.workflow_run.head_commit.message }}
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}.
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}.
branch: builds/facebook-fbsource
commit_user_name: ${{ github.actor }}
commit_user_email: ${{ github.actor }}@users.noreply.github.com
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
create_branch: true

View File

@@ -1,24 +1,19 @@
name: (Runtime) Fuzz tests
on:
schedule:
- cron: 0 * * * *
- cron: 0 * * * *
push:
branches:
- main
workflow_dispatch:
inputs:
prerelease_commit_sha:
required: false
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
test_fuzz:
if: inputs.prerelease_commit_sha == ''
runs-on: ubuntu-latest
env:
TZ: "/usr/share/zoneinfo/America/Los_Angeles"
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/setup-node@v4
@@ -32,5 +27,5 @@ jobs:
shell: bash
- name: Run fuzz tests
run: |-
FUZZ_TEST_SEED=$RANDOM yarn test fuzz --ci=github
FUZZ_TEST_SEED=$RANDOM yarn test --prod fuzz --ci=github
FUZZ_TEST_SEED=$RANDOM yarn test fuzz --ci
FUZZ_TEST_SEED=$RANDOM yarn test --prod fuzz --ci

View File

@@ -0,0 +1,50 @@
name: (Runtime) Publish Prereleases
on:
workflow_call:
inputs:
commit_sha:
required: true
default: ''
type: string
release_channel:
required: true
type: string
dist_tag:
required: true
type: string
secrets:
NPM_TOKEN:
required: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
publish_prerelease:
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: |
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}

View File

@@ -0,0 +1,47 @@
name: (Runtime) Publish Prereleases Manual
on:
workflow_dispatch:
inputs:
prerelease_commit_sha:
required: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: stable
# The tags to use when publishing canaries. The main one we should
# always include is "canary" but we can use multiple (e.g. alpha,
# beta, rc). To declare multiple, use a comma-separated string, like
# this:
# dist_tag: "canary,alpha,beta,rc"
#
# TODO: We currently tag canaries with "next" in addition to "canary"
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: canary,next,rc
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
# dist tags.
needs: publish_prerelease_canary
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: experimental
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -0,0 +1,35 @@
name: (Runtime) Publish Prereleases Nightly
on:
schedule:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next,rc
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
# dist tags.
needs: publish_prerelease_canary
with:
commit_sha: ${{ github.sha }}
release_channel: experimental
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -7,6 +7,8 @@ on:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
prettier:
@@ -16,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -34,7 +36,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -52,7 +54,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -61,7 +63,7 @@ jobs:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: ./scripts/circleci/check_license.sh
- run: ./scripts/ci/check_license.sh
test_print_warnings:
name: Test print warnings
@@ -70,7 +72,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
@@ -79,4 +81,4 @@ jobs:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: ./scripts/circleci/test_print_warnings.sh
- run: ./scripts/ci/test_print_warnings.sh

View File

@@ -4,6 +4,7 @@ on:
schedule:
# Run hourly
- cron: '0 * * * *'
workflow_dispatch:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -18,14 +18,15 @@ packages/react-devtools-timeline/static
# react compiler
compiler/**/dist
compiler/**/__tests__/fixtures/**/*.expect.md
compiler/**/__tests__/fixtures/**/*.flow.js
compiler/**/.next
# contains invalid graphql`...` which results in a promise rejection error from `yarn prettier-all`.
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
compiler/crates
compiler/apps/playground/public
compiler/**/LICENSE
compiler/.*
compiler/*.md*
compiler/*.json
compiler/*.css

View File

@@ -1,18 +1,15 @@
'use strict';
const {
compilerPaths,
esNextPaths,
typescriptPaths,
} = require('./scripts/shared/pathsByLanguageVersion');
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
module.exports = {
plugins: ['prettier-plugin-hermes-parser'],
bracketSpacing: false,
singleQuote: true,
bracketSameLine: true,
trailingComma: 'es5',
printWidth: 80,
parser: 'flow',
parser: 'hermes',
arrowParens: 'avoid',
overrides: [
{
@@ -28,25 +25,11 @@ module.exports = {
},
},
{
files: typescriptPaths,
files: ['*.ts', '*.tsx'],
options: {
trailingComma: 'all',
parser: 'typescript',
},
},
{
files: compilerPaths,
options: {
requirePragma: false,
parser: 'babel-ts',
semi: true,
singleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,
printWidth: 80,
arrowParens: 'always',
},
},
],
};

View File

@@ -1,4 +1,4 @@
# [React](https://react.dev/) &middot; [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebook/react/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/react.svg?style=flat)](https://www.npmjs.com/package/react) [![CircleCI Status](https://circleci.com/gh/facebook/react.svg?style=shield)](https://circleci.com/gh/facebook/react) [![(Runtime) Build and Test](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml/badge.svg)](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml) [![(Compiler) TypeScript](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml/badge.svg?branch=main)](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
# [React](https://react.dev/) &middot; [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebook/react/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/react.svg?style=flat)](https://www.npmjs.com/package/react) [![(Runtime) Build and Test](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml/badge.svg)](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml) [![(Compiler) TypeScript](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml/badge.svg?branch=main)](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
React is a JavaScript library for building user interfaces.

View File

@@ -25,7 +25,7 @@ const ReactVersion = '19.0.0';
// choose to change it to "alpha", "beta", "rc", etc.
//
// It only affects the label used in the version string. To customize the
// npm dist tags used during publish, refer to .circleci/config.yml.
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
const canaryChannelLabel = 'rc';
// If the canaryChannelLabel is "rc", the build pipeline will use this to build

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import { expect, test } from "@playwright/test";
import { encodeStore, type Store } from "../../lib/stores";
import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
const STORE: Store = {
source: `export default function TestComponent({ x }) {
@@ -17,33 +17,33 @@ const STORE: Store = {
const HASH = encodeStore(STORE);
function concat(data: Array<string>): string {
return data.join("");
return data.join('');
}
test("editor should compile successfully", async ({ page }) => {
await page.goto(`/#${HASH}`, { waitUntil: "networkidle" });
test('editor should compile successfully', async ({page}) => {
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
await page.screenshot({
fullPage: true,
path: "test-results/00-on-networkidle.png",
path: 'test-results/00-on-networkidle.png',
});
// User input from hash compiles
await page.screenshot({
fullPage: true,
path: "test-results/01-show-js-before.png",
path: 'test-results/01-show-js-before.png',
});
const userInput =
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
expect(concat(userInput)).toMatchSnapshot("user-input.txt");
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
// Reset button works
page.on("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Reset" }).click();
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', {name: 'Reset'}).click();
await page.screenshot({
fullPage: true,
path: "test-results/02-show-js-after.png",
path: 'test-results/02-show-js-after.png',
});
const defaultInput =
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
expect(concat(defaultInput)).toMatchSnapshot("default-input.txt");
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
});

View File

@@ -5,25 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/
import type { NextPage } from "next";
import Head from "next/head";
import { SnackbarProvider } from "notistack";
import { Editor, Header, StoreProvider } from "../components";
import MessageSnackbar from "../components/Message";
import type {NextPage} from 'next';
import Head from 'next/head';
import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';
const Home: NextPage = () => {
return (
<div className="flex flex-col w-screen h-screen font-light">
<Head>
<title>
{process.env.NODE_ENV === "development"
? "[DEV] React Compiler Playground"
: "React Compiler Playground"}
{process.env.NODE_ENV === 'development'
? '[DEV] React Compiler Playground'
: 'React Compiler Playground'}
</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
></meta>
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link
@@ -45,8 +44,7 @@ const Home: NextPage = () => {
<SnackbarProvider
preventDuplicate
maxSnack={10}
Components={{ message: MessageSnackbar }}
>
Components={{message: MessageSnackbar}}>
<Header />
<Editor />
</SnackbarProvider>

View File

@@ -5,26 +5,21 @@
* LICENSE file in the root directory of this source tree.
*/
import "../styles/globals.css";
import '../styles/globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
"use no memo";
export default function RootLayout({children}: {children: React.ReactNode}) {
'use no memo';
return (
<html lang="en">
<head>
<title>
{process.env.NODE_ENV === "development"
? "[DEV] React Compiler Playground"
: "React Compiler Playground"}
{process.env.NODE_ENV === 'development'
? '[DEV] React Compiler Playground'
: 'React Compiler Playground'}
</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
></meta>
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
"use client";
'use client';
import { SnackbarProvider } from "notistack";
import { Editor, Header, StoreProvider } from "../components";
import MessageSnackbar from "../components/Message";
import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';
export default function Hoot() {
return (
@@ -17,8 +17,7 @@ export default function Hoot() {
<SnackbarProvider
preventDuplicate
maxSnack={10}
Components={{ message: MessageSnackbar }}
>
Components={{message: MessageSnackbar}}>
<Header />
<Editor />
</SnackbarProvider>

View File

@@ -8,12 +8,12 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ["next/babel"],
presets: ['next/babel'],
plugins: [
[
"babel-plugin-react-compiler",
'babel-plugin-react-compiler',
{
runtimeModule: "react-compiler-runtime",
runtimeModule: 'react-compiler-runtime',
},
],
],

View File

@@ -11,86 +11,86 @@
module.exports = {
// Text colors
primary: "#23272F", // gray-90
"primary-dark": "#F6F7F9", // gray-5
secondary: "#404756", // gray-70
"secondary-dark": "#EBECF0", // gray-10
link: "#087EA4", // blue-50
"link-dark": "#149ECA", // blue-40
syntax: "#EBECF0", // gray-10
wash: "#FFFFFF",
"wash-dark": "#23272F", // gray-90
card: "#F6F7F9", // gray-05
"card-dark": "#343A46", // gray-80
highlight: "#E6F7FF", // blue-10
"highlight-dark": "rgba(88,175,223,.1)",
border: "#EBECF0", // gray-10
"border-dark": "#343A46", // gray-80
"secondary-button": "#EBECF0", // gray-10
"secondary-button-dark": "#404756", // gray-70
primary: '#23272F', // gray-90
'primary-dark': '#F6F7F9', // gray-5
secondary: '#404756', // gray-70
'secondary-dark': '#EBECF0', // gray-10
link: '#087EA4', // blue-50
'link-dark': '#149ECA', // blue-40
syntax: '#EBECF0', // gray-10
wash: '#FFFFFF',
'wash-dark': '#23272F', // gray-90
card: '#F6F7F9', // gray-05
'card-dark': '#343A46', // gray-80
highlight: '#E6F7FF', // blue-10
'highlight-dark': 'rgba(88,175,223,.1)',
border: '#EBECF0', // gray-10
'border-dark': '#343A46', // gray-80
'secondary-button': '#EBECF0', // gray-10
'secondary-button-dark': '#404756', // gray-70
// Gray
"gray-95": "#16181D",
"gray-90": "#23272F",
"gray-80": "#343A46",
"gray-70": "#404756",
"gray-60": "#4E5769",
"gray-50": "#5E687E", // unused
"gray-40": "#78839B",
"gray-30": "#99A1B3",
"gray-20": "#BCC1CD",
"gray-10": "#EBECF0",
"gray-5": "#F6F7F9",
'gray-95': '#16181D',
'gray-90': '#23272F',
'gray-80': '#343A46',
'gray-70': '#404756',
'gray-60': '#4E5769',
'gray-50': '#5E687E', // unused
'gray-40': '#78839B',
'gray-30': '#99A1B3',
'gray-20': '#BCC1CD',
'gray-10': '#EBECF0',
'gray-5': '#F6F7F9',
// Blue
"blue-60": "#045975",
"blue-50": "#087EA4",
"blue-40": "#149ECA", // Brand Blue
"blue-30": "#58C4DC", // unused
"blue-20": "#ABE2ED",
"blue-10": "#E6F7FF", // todo: doesn't match illustrations
"blue-5": "#E6F6FA",
'blue-60': '#045975',
'blue-50': '#087EA4',
'blue-40': '#149ECA', // Brand Blue
'blue-30': '#58C4DC', // unused
'blue-20': '#ABE2ED',
'blue-10': '#E6F7FF', // todo: doesn't match illustrations
'blue-5': '#E6F6FA',
// Yellow
"yellow-60": "#B65700",
"yellow-50": "#C76A15",
"yellow-40": "#DB7D27", // unused
"yellow-30": "#FABD62", // unused
"yellow-20": "#FCDEB0", // unused
"yellow-10": "#FDE7C7",
"yellow-5": "#FEF5E7",
'yellow-60': '#B65700',
'yellow-50': '#C76A15',
'yellow-40': '#DB7D27', // unused
'yellow-30': '#FABD62', // unused
'yellow-20': '#FCDEB0', // unused
'yellow-10': '#FDE7C7',
'yellow-5': '#FEF5E7',
// Purple
"purple-60": "#2B3491", // unused
"purple-50": "#575FB7",
"purple-40": "#6B75DB",
"purple-30": "#8891EC",
"purple-20": "#C3C8F5", // unused
"purple-10": "#E7E9FB",
"purple-5": "#F3F4FD",
'purple-60': '#2B3491', // unused
'purple-50': '#575FB7',
'purple-40': '#6B75DB',
'purple-30': '#8891EC',
'purple-20': '#C3C8F5', // unused
'purple-10': '#E7E9FB',
'purple-5': '#F3F4FD',
// Green
"green-60": "#2B6E62",
"green-50": "#388F7F",
"green-40": "#44AC99",
"green-30": "#7FCCBF",
"green-20": "#ABDED5",
"green-10": "#E5F5F2",
"green-5": "#F4FBF9",
'green-60': '#2B6E62',
'green-50': '#388F7F',
'green-40': '#44AC99',
'green-30': '#7FCCBF',
'green-20': '#ABDED5',
'green-10': '#E5F5F2',
'green-5': '#F4FBF9',
// RED
"red-60": "#712D28",
"red-50": "#A6423A", // unused
"red-40": "#C1554D",
"red-30": "#D07D77",
"red-20": "#E5B7B3", // unused
"red-10": "#F2DBD9", // unused
"red-5": "#FAF1F0",
'red-60': '#712D28',
'red-50': '#A6423A', // unused
'red-40': '#C1554D',
'red-30': '#D07D77',
'red-20': '#E5B7B3', // unused
'red-10': '#F2DBD9', // unused
'red-5': '#FAF1F0',
// MISC
"code-block": "#99a1b30f", // gray-30 @ 6%
"gradient-blue": "#58C4DC", // Only used for the landing gradient for now.
'code-block': '#99a1b30f', // gray-30 @ 6%
'gradient-blue': '#58C4DC', // Only used for the landing gradient for now.
github: {
highlight: "#fffbdd",
highlight: '#fffbdd',
},
};

View File

@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import { parse as babelParse, ParserPlugin } from "@babel/parser";
import * as HermesParser from "hermes-parser";
import traverse, { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import {parse as babelParse, ParserPlugin} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
import traverse, {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetail,
@@ -20,51 +20,51 @@ import {
run,
ValueKind,
type Hook,
} from "babel-plugin-react-compiler/src";
import { type ReactFunctionType } from "babel-plugin-react-compiler/src/HIR/Environment";
import clsx from "clsx";
import invariant from "invariant";
import { useSnackbar } from "notistack";
import { useDeferredValue, useMemo } from "react";
import { useMountEffect } from "../../hooks";
import { defaultStore } from "../../lib/defaultStore";
} from 'babel-plugin-react-compiler/src';
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
import {useDeferredValue, useMemo} from 'react';
import {useMountEffect} from '../../hooks';
import {defaultStore} from '../../lib/defaultStore';
import {
createMessage,
initStoreFromUrlOrLocalStorage,
MessageLevel,
MessageSource,
type Store,
} from "../../lib/stores";
import { useStore, useStoreDispatch } from "../StoreContext";
import Input from "./Input";
} from '../../lib/stores';
import {useStore, useStoreDispatch} from '../StoreContext';
import Input from './Input';
import {
CompilerOutput,
default as Output,
PrintedCompilerPipelineValue,
} from "./Output";
import { printFunctionWithOutlined } from "babel-plugin-react-compiler/src/HIR/PrintHIR";
import { printReactiveFunctionWithOutlined } from "babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction";
} from './Output';
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
function parseInput(input: string, language: "flow" | "typescript") {
function parseInput(input: string, language: 'flow' | 'typescript') {
// Extract the first line to quickly check for custom test directives
if (language === "flow") {
if (language === 'flow') {
return HermesParser.parse(input, {
babel: true,
flow: "all",
sourceType: "module",
flow: 'all',
sourceType: 'module',
enableExperimentalComponentSyntax: true,
});
} else {
return babelParse(input, {
plugins: ["typescript", "jsx"],
sourceType: "module",
plugins: ['typescript', 'jsx'],
sourceType: 'module',
});
}
}
function parseFunctions(
source: string,
language: "flow" | "typescript"
language: 'flow' | 'typescript',
): Array<
NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -105,7 +105,7 @@ function parseFunctions(
const COMMON_HOOKS: Array<[string, Hook]> = [
[
"useFragment",
'useFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
@@ -114,7 +114,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
},
],
[
"usePaginationFragment",
'usePaginationFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
@@ -123,7 +123,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
},
],
[
"useRefetchableFragment",
'useRefetchableFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
@@ -132,7 +132,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
},
],
[
"useLazyLoadQuery",
'useLazyLoadQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
@@ -141,7 +141,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
},
],
[
"usePreloadedQuery",
'usePreloadedQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
@@ -156,22 +156,22 @@ function isHookName(s: string): boolean {
}
function getReactFunctionType(
id: NodePath<t.Identifier | null | undefined>
id: NodePath<t.Identifier | null | undefined>,
): ReactFunctionType {
if (id && id.node && id.isIdentifier()) {
if (isHookName(id.node.name)) {
return "Hook";
return 'Hook';
}
const isPascalCaseNameSpace = /^[A-Z].*/;
if (isPascalCaseNameSpace.test(id.node.name)) {
return "Component";
return 'Component';
}
}
return "Other";
return 'Other';
}
function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, PrintedCompilerPipelineValue[]>();
const error = new CompilerError();
const upsert = (result: PrintedCompilerPipelineValue) => {
@@ -182,15 +182,15 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
results.set(result.name, [result]);
}
};
let language: "flow" | "typescript";
let language: 'flow' | 'typescript';
if (source.match(/\@flow/)) {
language = "flow";
language = 'flow';
} else {
language = "typescript";
language = 'typescript';
}
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf("\n"));
const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragma(pragma);
for (const fn of parseFunctions(source, language)) {
@@ -199,16 +199,16 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
new CompilerErrorDetail({
reason: `Unexpected function type ${fn.node.type}`,
description:
"Playground only supports parsing function declarations",
'Playground only supports parsing function declarations',
severity: ErrorSeverity.Todo,
loc: fn.node.loc ?? null,
suggestions: null,
})
}),
);
continue;
}
const id = fn.get("id");
const id = fn.get('id');
for (const result of run(
fn,
{
@@ -216,20 +216,20 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
"_c",
'_c',
null,
null,
null,
null
)) {
const fnName = fn.node.id?.name ?? null;
switch (result.kind) {
case "ast": {
case 'ast': {
upsert({
kind: "ast",
kind: 'ast',
fnName,
name: result.name,
value: {
type: "FunctionDeclaration",
type: 'FunctionDeclaration',
id: result.value.id,
async: result.value.async,
generator: result.value.generator,
@@ -239,27 +239,27 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
});
break;
}
case "hir": {
case 'hir': {
upsert({
kind: "hir",
kind: 'hir',
fnName,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case "reactive": {
case 'reactive': {
upsert({
kind: "reactive",
kind: 'reactive',
fnName,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case "debug": {
case 'debug': {
upsert({
kind: "debug",
kind: 'debug',
fnName,
name: result.name,
value: result.value,
@@ -288,24 +288,24 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
reason: `Unexpected failure when transforming input! ${err}`,
loc: null,
suggestions: null,
})
}),
);
}
}
if (error.hasErrors()) {
return [{ kind: "err", results, error: error }, language];
return [{kind: 'err', results, error: error}, language];
}
return [{ kind: "ok", results }, language];
return [{kind: 'ok', results}, language];
}
export default function Editor() {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar } = useSnackbar();
const {enqueueSnackbar} = useSnackbar();
const [compilerOutput, language] = useMemo(
() => compile(deferredStore.source),
[deferredStore.source]
[deferredStore.source],
);
useMountEffect(() => {
@@ -313,35 +313,35 @@ export default function Editor() {
try {
mountStore = initStoreFromUrlOrLocalStorage();
} catch (e) {
invariant(e instanceof Error, "Only Error may be caught.");
invariant(e instanceof Error, 'Only Error may be caught.');
enqueueSnackbar(e.message, {
variant: "message",
variant: 'message',
...createMessage(
"Bad URL - fell back to the default Playground.",
'Bad URL - fell back to the default Playground.',
MessageLevel.Info,
MessageSource.Playground
MessageSource.Playground,
),
});
mountStore = defaultStore;
}
dispatchStore({
type: "setStore",
payload: { store: mountStore },
type: 'setStore',
payload: {store: mountStore},
});
});
return (
<>
<div className="relative flex basis top-14">
<div className={clsx("relative sm:basis-1/4")}>
<div className={clsx('relative sm:basis-1/4')}>
<Input
language={language}
errors={
compilerOutput.kind === "err" ? compilerOutput.error.details : []
compilerOutput.kind === 'err' ? compilerOutput.error.details : []
}
/>
</div>
<div className={clsx("flex sm:flex flex-wrap")}>
<div className={clsx('flex sm:flex flex-wrap')}>
<Output store={deferredStore} compilerOutput={compilerOutput} />
</div>
</div>

View File

@@ -5,28 +5,28 @@
* LICENSE file in the root directory of this source tree.
*/
import MonacoEditor, { loader, type Monaco } from "@monaco-editor/react";
import { CompilerErrorDetail } from "babel-plugin-react-compiler/src";
import invariant from "invariant";
import type { editor } from "monaco-editor";
import * as monaco from "monaco-editor";
import { Resizable } from "re-resizable";
import { useEffect, useState } from "react";
import { renderReactCompilerMarkers } from "../../lib/reactCompilerMonacoDiagnostics";
import { useStore, useStoreDispatch } from "../StoreContext";
import { monacoOptions } from "./monacoOptions";
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
import invariant from 'invariant';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import {Resizable} from 're-resizable';
import {useEffect, useState} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
// @ts-ignore
import React$Types from "../../node_modules/@types/react/index.d.ts";
import React$Types from '../../node_modules/@types/react/index.d.ts';
loader.config({ monaco });
loader.config({monaco});
type Props = {
errors: CompilerErrorDetail[];
language: "flow" | "typescript";
language: 'flow' | 'typescript';
};
export default function Input({ errors, language }: Props) {
export default function Input({errors, language}: Props) {
const [monaco, setMonaco] = useState<Monaco | null>(null);
const store = useStore();
const dispatchStore = useStoreDispatch();
@@ -36,11 +36,11 @@ export default function Input({ errors, language }: Props) {
if (!monaco) return;
const uri = monaco.Uri.parse(`file:///index.js`);
const model = monaco.editor.getModel(uri);
invariant(model, "Model must exist for the selected input file.");
renderReactCompilerMarkers({ monaco, model, details: errors });
invariant(model, 'Model must exist for the selected input file.');
renderReactCompilerMarkers({monaco, model, details: errors});
// N.B. that `tabSize` is a model property, not an editor property.
// So, the tab size has to be set per model.
model.updateOptions({ tabSize: 2 });
model.updateOptions({tabSize: 2});
}, [monaco, errors]);
const flowDiagnosticDisable = [
@@ -64,11 +64,11 @@ export default function Input({ errors, language }: Props) {
8011,
8012,
8013,
...(language === "flow" ? flowDiagnosticDisable : []),
...(language === 'flow' ? flowDiagnosticDisable : []),
],
noSemanticValidation: true,
// Monaco can't validate Flow component syntax
noSyntaxValidation: language === "flow",
noSyntaxValidation: language === 'flow',
});
}, [monaco, language]);
@@ -76,7 +76,7 @@ export default function Input({ errors, language }: Props) {
if (!value) return;
dispatchStore({
type: "updateFile",
type: 'updateFile',
payload: {
source: value,
},
@@ -91,11 +91,11 @@ export default function Input({ errors, language }: Props) {
target: monaco.languages.typescript.ScriptTarget.ES2015,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
jsx: monaco.languages.typescript.JsxEmit.Preserve,
typeRoots: ["node_modules/@types"],
typeRoots: ['node_modules/@types'],
allowSyntheticDefaultImports: true,
};
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
tscOptions
tscOptions,
);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...tscOptions,
@@ -106,7 +106,7 @@ export default function Input({ errors, language }: Props) {
// Add React type declarations to Monaco
const reactLib = [
React$Types,
"file:///node_modules/@types/react/index.d.ts",
'file:///node_modules/@types/react/index.d.ts',
] as [any, string];
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
@@ -124,17 +124,16 @@ export default function Input({ errors, language }: Props) {
<div className="relative flex flex-col flex-none border-r border-gray-200">
<Resizable
minWidth={650}
enable={{ right: true }}
enable={{right: true}}
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
className="!h-[calc(100vh_-_3.5rem)]"
>
className="!h-[calc(100vh_-_3.5rem)]">
<MonacoEditor
path={"index.js"}
path={'index.js'}
// .js and .jsx files are specified to be TS so that Monaco can actually
// check their syntax using its TS language service. They are still JS files
// due to their extensions, so TS language features don't work.
language={"javascript"}
language={'javascript'}
value={store.source}
onMount={handleMount}
onChange={handleChange}

View File

@@ -5,46 +5,46 @@
* LICENSE file in the root directory of this source tree.
*/
import generate from "@babel/generator";
import * as t from "@babel/types";
import generate from '@babel/generator';
import * as t from '@babel/types';
import {
CodeIcon,
DocumentAddIcon,
InformationCircleIcon,
} from "@heroicons/react/outline";
import MonacoEditor, { DiffEditor } from "@monaco-editor/react";
import { type CompilerError } from "babel-plugin-react-compiler/src";
import parserBabel from "prettier/plugins/babel";
import * as prettierPluginEstree from "prettier/plugins/estree";
import * as prettier from "prettier/standalone";
import { memo, useEffect, useState } from "react";
import { type Store } from "../../lib/stores";
import TabbedWindow from "../TabbedWindow";
import { monacoOptions } from "./monacoOptions";
} from '@heroicons/react/outline';
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
import {type CompilerError} from 'babel-plugin-react-compiler/src';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {memo, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
export type PrintedCompilerPipelineValue =
| {
kind: "ast";
kind: 'ast';
name: string;
fnName: string | null;
value: t.FunctionDeclaration;
}
| {
kind: "hir";
kind: 'hir';
name: string;
fnName: string | null;
value: string;
}
| { kind: "reactive"; name: string; fnName: string | null; value: string }
| { kind: "debug"; name: string; fnName: string | null; value: string };
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
| {kind: 'debug'; name: string; fnName: string | null; value: string};
export type CompilerOutput =
| { kind: "ok"; results: Map<string, PrintedCompilerPipelineValue[]> }
| {kind: 'ok'; results: Map<string, PrintedCompilerPipelineValue[]>}
| {
kind: "err";
kind: 'err';
results: Map<string, PrintedCompilerPipelineValue[]>;
error: CompilerError;
};
@@ -63,7 +63,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
switch (result.kind) {
case "hir": {
case 'hir': {
const prev = concattedResults.get(result.name);
const next = result.value;
const identName = `function ${result.fnName}`;
@@ -74,7 +74,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
}
break;
}
case "reactive": {
case 'reactive': {
const prev = concattedResults.get(passName);
const next = result.value;
if (prev != null) {
@@ -84,30 +84,29 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
}
break;
}
case "ast":
case 'ast':
topLevelFnDecls.push(result.value);
break;
case "debug": {
case 'debug': {
concattedResults.set(passName, result.value);
break;
}
default: {
const _: never = result;
throw new Error("Unexpected result kind");
throw new Error('Unexpected result kind');
}
}
}
}
let lastPassOutput: string | null = null;
let nonDiffPasses = ["HIR", "BuildReactiveFunction", "EnvironmentConfig"];
let nonDiffPasses = ['HIR', 'BuildReactiveFunction', 'EnvironmentConfig'];
for (const [passName, text] of concattedResults) {
tabs.set(
passName,
<TextTabContent
output={text}
diff={lastPassOutput}
showInfoPanel={!nonDiffPasses.includes(passName)}
></TextTabContent>
showInfoPanel={!nonDiffPasses.includes(passName)}></TextTabContent>,
);
lastPassOutput = text;
}
@@ -116,25 +115,24 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
// Make a synthetic Program so we can have a single AST with all the top level
// FunctionDeclarations
const ast = t.program(topLevelFnDecls);
const { code, sourceMapUrl } = await codegen(ast, source);
const {code, sourceMapUrl} = await codegen(ast, source);
reorderedTabs.set(
"JS",
'JS',
<TextTabContent
output={code}
diff={null}
showInfoPanel={false}
></TextTabContent>
showInfoPanel={false}></TextTabContent>,
);
if (sourceMapUrl) {
reorderedTabs.set(
"SourceMap",
'SourceMap',
<>
<iframe
src={sourceMapUrl}
className="w-full h-monaco_small sm:h-monaco"
title="Generated Code"
/>
</>
</>,
);
}
}
@@ -146,23 +144,23 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
async function codegen(
ast: t.Program,
source: string
): Promise<{ code: any; sourceMapUrl: string | null }> {
source: string,
): Promise<{code: any; sourceMapUrl: string | null}> {
const generated = generate(
ast,
{ sourceMaps: true, sourceFileName: "input.js" },
source
{sourceMaps: true, sourceFileName: 'input.js'},
source,
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map)
JSON.stringify(generated.map),
);
const codegenOutput = await prettier.format(generated.code, {
semi: true,
parser: "babel",
parser: 'babel',
plugins: [parserBabel, prettierPluginEstree],
});
return { code: codegenOutput, sourceMapUrl };
return {code: codegenOutput, sourceMapUrl};
}
function utf16ToUTF8(s: string): string {
@@ -173,27 +171,27 @@ function getSourceMapUrl(code: string, map: string): string | null {
code = utf16ToUTF8(code);
map = utf16ToUTF8(map);
return `https://evanw.github.io/source-map-visualization/#${btoa(
`${code.length}\0${code}${map.length}\0${map}`
`${code.length}\0${code}${map.length}\0${map}`,
)}`;
}
function Output({ store, compilerOutput }: Props) {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(["JS"]));
function Output({store, compilerOutput}: Props) {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map()
() => new Map(),
);
useEffect(() => {
tabify(store.source, compilerOutput).then((tabs) => {
tabify(store.source, compilerOutput).then(tabs => {
setTabs(tabs);
});
}, [store.source, compilerOutput]);
const changedPasses: Set<string> = new Set(["JS", "HIR"]); // Initial and final passes should always be bold
let lastResult: string = "";
const changedPasses: Set<string> = new Set(['JS', 'HIR']); // Initial and final passes should always be bold
let lastResult: string = '';
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
let currResult = "";
if (result.kind === "hir" || result.kind === "reactive") {
let currResult = '';
if (result.kind === 'hir' || result.kind === 'reactive') {
currResult += `function ${result.fnName}\n\n${result.value}`;
}
if (currResult !== lastResult) {
@@ -212,18 +210,16 @@ function Output({ store, compilerOutput }: Props) {
tabs={tabs}
changedPasses={changedPasses}
/>
{compilerOutput.kind === "err" ? (
{compilerOutput.kind === 'err' ? (
<div
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
style={{ width: "calc(100vw - 650px)" }}
>
style={{width: 'calc(100vw - 650px)'}}>
<div className="w-full p-4 basis-full border-b">
<h2>COMPILER ERRORS</h2>
</div>
<pre
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
style={{ width: "calc(100vw - 650px)", height: "150px" }}
>
style={{width: 'calc(100vw - 650px)', height: '150px'}}>
<code>{compilerOutput.error.toString()}</code>
</pre>
</div>
@@ -251,8 +247,7 @@ function TextTabContent({
{diff != null && output !== diff ? (
<button
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={() => setDiffMode((diffMode) => !diffMode)}
>
onClick={() => setDiffMode(diffMode => !diffMode)}>
{!diffMode ? (
<>
<DocumentAddIcon className="w-5 h-5" /> Show Diff
@@ -280,7 +275,7 @@ function TextTabContent({
options={{
...monacoOptions,
readOnly: true,
lineNumbers: "off",
lineNumbers: 'off',
glyphMargin: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,
@@ -294,7 +289,7 @@ function TextTabContent({
options={{
...monacoOptions,
readOnly: true,
lineNumbers: "off",
lineNumbers: 'off',
glyphMargin: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import dynamic from "next/dynamic";
import dynamic from 'next/dynamic';
// monaco-editor is currently not compatible with ssr
// https://github.com/vercel/next.js/issues/31692
const Editor = dynamic(() => import("./EditorImpl"), {
const Editor = dynamic(() => import('./EditorImpl'), {
ssr: false,
});

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import type { EditorProps } from "@monaco-editor/react";
import type {EditorProps} from '@monaco-editor/react';
export const monacoOptions: Partial<EditorProps["options"]> = {
export const monacoOptions: Partial<EditorProps['options']> = {
fontSize: 14,
padding: { top: 8 },
padding: {top: 8},
scrollbar: {
verticalScrollbarSize: 10,
alwaysConsumeMouseWheel: false,
@@ -22,11 +22,11 @@ export const monacoOptions: Partial<EditorProps["options"]> = {
fontFamily: '"Source Code Pro", monospace',
glyphMargin: true,
autoClosingBrackets: "languageDefined",
autoClosingDelete: "always",
autoClosingOvertype: "always",
autoClosingBrackets: 'languageDefined',
autoClosingDelete: 'always',
autoClosingOvertype: 'always',
automaticLayout: true,
wordWrap: "on",
wrappingIndent: "deepIndent",
wordWrap: 'on',
wrappingIndent: 'deepIndent',
};

View File

@@ -5,24 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/
import { RefreshIcon, ShareIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/solid";
import clsx from "clsx";
import Link from "next/link";
import { useSnackbar } from "notistack";
import { useState } from "react";
import { defaultStore } from "../lib/defaultStore";
import { IconGitHub } from "./Icons/IconGitHub";
import Logo from "./Logo";
import { useStoreDispatch } from "./StoreContext";
import {RefreshIcon, ShareIcon} from '@heroicons/react/outline';
import {CheckIcon} from '@heroicons/react/solid';
import clsx from 'clsx';
import Link from 'next/link';
import {useSnackbar} from 'notistack';
import {useState} from 'react';
import {defaultStore} from '../lib/defaultStore';
import {IconGitHub} from './Icons/IconGitHub';
import Logo from './Logo';
import {useStoreDispatch} from './StoreContext';
export default function Header() {
const [showCheck, setShowCheck] = useState(false);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
const handleReset = () => {
if (confirm("Are you sure you want to reset the playground?")) {
if (confirm('Are you sure you want to reset the playground?')) {
/*
Close open snackbars if any. This is necessary because when displaying
outputs (Preview or not), we only close previous snackbars if we received
@@ -31,13 +31,13 @@ export default function Header() {
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
*/
closeSnackbar();
dispatchStore({ type: "setStore", payload: { store: defaultStore } });
dispatchStore({type: 'setStore', payload: {store: defaultStore}});
}
};
const handleShare = () => {
navigator.clipboard.writeText(location.href).then(() => {
enqueueSnackbar("URL copied to clipboard");
enqueueSnackbar('URL copied to clipboard');
setShowCheck(true);
// Show the check mark icon briefly after URL is copied
setTimeout(() => setShowCheck(false), 1000);
@@ -49,8 +49,8 @@ export default function Header() {
<div className="flex items-center flex-none h-full gap-2 text-lg">
<Logo
className={clsx(
"w-8 h-8 text-link",
process.env.NODE_ENV === "development" && "text-yellow-600"
'w-8 h-8 text-link',
process.env.NODE_ENV === 'development' && 'text-yellow-600',
)}
/>
<p className="hidden select-none sm:block">React Compiler Playground</p>
@@ -60,8 +60,7 @@ export default function Header() {
title="Reset Playground"
aria-label="Reset Playground"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={handleReset}
>
onClick={handleReset}>
<RefreshIcon className="w-5 h-5" />
<p className="hidden sm:block">Reset</p>
</button>
@@ -70,8 +69,7 @@ export default function Header() {
aria-label="Copy sharable URL"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={handleShare}
disabled={showCheck}
>
disabled={showCheck}>
{!showCheck ? (
<ShareIcon className="w-5 h-5" />
) : (
@@ -84,8 +82,7 @@ export default function Header() {
target="_blank"
rel="noreferrer noopener"
aria-label="Open on GitHub"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
>
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link">
<IconGitHub />
</Link>
</div>

View File

@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import { memo } from "react";
import {memo} from 'react';
export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
export const IconGitHub = memo<JSX.IntrinsicElements['svg']>(
function IconGitHub(props) {
return (
<svg
@@ -16,10 +16,9 @@ export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
height="1.5em"
viewBox="0 -2 24 24"
fill="currentColor"
{...props}
>
{...props}>
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
</svg>
);
}
},
);

View File

@@ -7,14 +7,13 @@
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
export default function Logo(props: JSX.IntrinsicElements["svg"]) {
export default function Logo(props: JSX.IntrinsicElements['svg']) {
return (
<svg
viewBox="0 0 410 369"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
{...props}>
<path
d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z"
fill="currentColor"

View File

@@ -10,13 +10,13 @@ import {
ExclamationIcon,
InformationCircleIcon,
XIcon,
} from "@heroicons/react/solid";
import { CustomContentProps, SnackbarContent, useSnackbar } from "notistack";
import { forwardRef } from "react";
import { MessageLevel, MessageSource } from "../lib/stores";
} from '@heroicons/react/solid';
import {CustomContentProps, SnackbarContent, useSnackbar} from 'notistack';
import {forwardRef} from 'react';
import {MessageLevel, MessageSource} from '../lib/stores';
// https://notistack.com/examples/advanced/custom-component#custom-variant-(typescript)
declare module "notistack" {
declare module 'notistack' {
interface VariantOverrides {
message: {
title: string;
@@ -34,15 +34,14 @@ interface MessageProps extends CustomContentProps {
}
const Message = forwardRef<HTMLDivElement, MessageProps>(
({ id, title, level, source, codeframe }, ref) => {
const { closeSnackbar } = useSnackbar();
({id, title, level, source, codeframe}, ref) => {
const {closeSnackbar} = useSnackbar();
const isDismissible = source !== MessageSource.Playground;
return (
<SnackbarContent
ref={ref}
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast"
>
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast">
<div className="flex gap-3 w-toast-body">
{level === MessageLevel.Warning ? (
<div className="flex items-center justify-center flex-none rounded-md w-7 h-7 bg-amber-100">
@@ -69,16 +68,15 @@ const Message = forwardRef<HTMLDivElement, MessageProps>(
{isDismissible ? (
<button
className="flex items-center justify-center flex-none transition-colors duration-150 ease-in rounded-md justify-self-end group w-7 h-7 hover:bg-gray-200"
onClick={() => closeSnackbar(id)}
>
onClick={() => closeSnackbar(id)}>
<XIcon className="w-5 h-5 fill-gray-500 group-hover:fill-gray-800" />
</button>
) : null}
</SnackbarContent>
);
}
},
);
Message.displayName = "MessageComponent";
Message.displayName = 'MessageComponent';
export default Message;

View File

@@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import type { Dispatch, ReactNode } from "react";
import { useReducer } from "react";
import createContext from "../lib/createContext";
import { emptyStore } from "../lib/defaultStore";
import type { Store } from "../lib/stores";
import { saveStore } from "../lib/stores";
import type {Dispatch, ReactNode} from 'react';
import {useReducer} from 'react';
import createContext from '../lib/createContext';
import {emptyStore} from '../lib/defaultStore';
import type {Store} from '../lib/stores';
import {saveStore} from '../lib/stores';
const StoreContext = createContext<Store>();
@@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
/**
* Make Store and dispatch function available to all sub-components in children.
*/
export function StoreProvider({ children }: { children: ReactNode }) {
export function StoreProvider({children}: {children: ReactNode}) {
const [store, dispatch] = useReducer(storeReducer, emptyStore);
return (
@@ -43,13 +43,13 @@ export function StoreProvider({ children }: { children: ReactNode }) {
type ReducerAction =
| {
type: "setStore";
type: 'setStore';
payload: {
store: Store;
};
}
| {
type: "updateFile";
type: 'updateFile';
payload: {
source: string;
};
@@ -57,14 +57,14 @@ type ReducerAction =
function storeReducer(store: Store, action: ReducerAction): Store {
switch (action.type) {
case "setStore": {
case 'setStore': {
const newStore = action.payload.store;
saveStore(newStore);
return newStore;
}
case "updateFile": {
const { source } = action.payload;
case 'updateFile': {
const {source} = action.payload;
const newStore = {
...store,

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import { Resizable } from "re-resizable";
import React, { useCallback } from "react";
import {Resizable} from 're-resizable';
import React, {useCallback} from 'react';
type TabsRecord = Map<string, React.ReactNode>;
@@ -21,15 +21,14 @@ export default function TabbedWindow(props: {
return (
<div
className="flex items-center justify-center"
style={{ width: "calc(100vw - 650px)" }}
>
style={{width: 'calc(100vw - 650px)'}}>
No compiler output detected, see errors below
</div>
);
}
return (
<div className="flex flex-row">
{Array.from(props.tabs.keys()).map((name) => {
{Array.from(props.tabs.keys()).map(name => {
return (
<TabbedWindowItem
name={name}
@@ -73,15 +72,14 @@ function TabbedWindowItem({
return (
<div key={name} className="flex flex-row">
{isShow ? (
<Resizable className="border-r" minWidth={550} enable={{ right: true }}>
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? "font-bold" : "font-light"
} text-secondary hover:text-link`}
>
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {name}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
@@ -91,12 +89,11 @@ function TabbedWindowItem({
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{ transform: "rotate(90deg) translate(-50%)" }}
style={{transform: 'rotate(90deg) translate(-50%)'}}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? "font-bold" : "font-light"
} text-secondary hover:text-link`}
>
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{name}
</button>
</div>

View File

@@ -5,6 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
export { default as Editor } from "./Editor";
export { default as Header } from "./Header";
export { StoreProvider } from "./StoreContext";
export {default as Editor} from './Editor';
export {default as Header} from './Header';
export {StoreProvider} from './StoreContext';

View File

@@ -5,4 +5,4 @@
* LICENSE file in the root directory of this source tree.
*/
export { default as useMountEffect } from "./useMountEffect";
export {default as useMountEffect} from './useMountEffect';

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type { EffectCallback } from "react";
import { useEffect } from "react";
import type {EffectCallback} from 'react';
import {useEffect} from 'react';
export default function useMountEffect(effect: EffectCallback) {
return useEffect(effect, []);

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import React from 'react';
/**
* Replacement to React.createContext.
@@ -29,9 +29,9 @@ export default function createContext<T>() {
function useContext() {
const c = React.useContext(context);
if (!c)
throw new Error("useContext must be within a Provider with a value");
throw new Error('useContext must be within a Provider with a value');
return c;
}
return { useContext, Provider: context.Provider };
return {useContext, Provider: context.Provider};
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type { Store } from "./stores";
import type {Store} from './stores';
const index = `\
export default function MyApp() {
@@ -18,5 +18,5 @@ export const defaultStore: Store = {
};
export const emptyStore: Store = {
source: "",
source: '',
};

View File

@@ -5,16 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
import { Monaco } from "@monaco-editor/react";
import {Monaco} from '@monaco-editor/react';
import {
CompilerErrorDetail,
ErrorSeverity,
} from "babel-plugin-react-compiler/src";
import { MarkerSeverity, type editor } from "monaco-editor";
} from 'babel-plugin-react-compiler/src';
import {MarkerSeverity, type editor} from 'monaco-editor';
function mapReactCompilerSeverityToMonaco(
level: ErrorSeverity,
monaco: Monaco
monaco: Monaco,
): MarkerSeverity {
switch (level) {
case ErrorSeverity.Todo:
@@ -26,9 +26,9 @@ function mapReactCompilerSeverityToMonaco(
function mapReactCompilerDiagnosticToMonacoMarker(
detail: CompilerErrorDetail,
monaco: Monaco
monaco: Monaco,
): editor.IMarkerData | null {
if (detail.loc == null || typeof detail.loc === "symbol") {
if (detail.loc == null || typeof detail.loc === 'symbol') {
return null;
}
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
@@ -63,27 +63,27 @@ export function renderReactCompilerMarkers({
markers.push(marker);
}
if (markers.length > 0) {
monaco.editor.setModelMarkers(model, "owner", markers);
const newDecorations = markers.map((marker) => {
monaco.editor.setModelMarkers(model, 'owner', markers);
const newDecorations = markers.map(marker => {
return {
range: new monaco.Range(
marker.startLineNumber,
marker.startColumn,
marker.endLineNumber,
marker.endColumn
marker.endColumn,
),
options: {
isWholeLine: true,
glyphMarginClassName: "bg-red-300",
glyphMarginClassName: 'bg-red-300',
},
};
});
decorations = model.deltaDecorations(decorations, newDecorations);
} else {
monaco.editor.setModelMarkers(model, "owner", []);
monaco.editor.setModelMarkers(model, 'owner', []);
decorations = model.deltaDecorations(
model.getAllDecorations().map((d) => d.id),
[]
model.getAllDecorations().map(d => d.id),
[],
);
}
}

View File

@@ -5,5 +5,5 @@
* LICENSE file in the root directory of this source tree.
*/
export * from "./messages";
export * from "./store";
export * from './messages';
export * from './store';

View File

@@ -27,10 +27,10 @@ export interface Message {
export function createMessage(
message: string,
level: MessageLevel,
source: MessageSource
source: MessageSource,
): Message {
const [title, ...body] = message.split("\n");
const codeframe = body.length > 0 ? body.join("\n") : undefined;
const [title, ...body] = message.split('\n');
const codeframe = body.length > 0 ? body.join('\n') : undefined;
return {
source,

View File

@@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import invariant from "invariant";
import invariant from 'invariant';
import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from "lz-string";
import { defaultStore } from "../defaultStore";
} from 'lz-string';
import {defaultStore} from '../defaultStore';
/**
* Global Store for Playground
@@ -30,8 +30,8 @@ export function decodeStore(hash: string): Store {
*/
export function saveStore(store: Store) {
const hash = encodeStore(store);
localStorage.setItem("playgroundStore", hash);
history.replaceState({}, "", `#${hash}`);
localStorage.setItem('playgroundStore', hash);
history.replaceState({}, '', `#${hash}`);
}
/**
@@ -41,9 +41,9 @@ export function saveStore(store: Store) {
function isValidStore(raw: unknown): raw is Store {
return (
raw != null &&
typeof raw == "object" &&
"source" in raw &&
typeof raw["source"] === "string"
typeof raw == 'object' &&
'source' in raw &&
typeof raw['source'] === 'string'
);
}
@@ -53,7 +53,7 @@ function isValidStore(raw: unknown): raw is Store {
*/
export function initStoreFromUrlOrLocalStorage(): Store {
const encodedSourceFromUrl = location.hash.slice(1);
const encodedSourceFromLocal = localStorage.getItem("playgroundStore");
const encodedSourceFromLocal = localStorage.getItem('playgroundStore');
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;
// No data in the URL and no data in the localStorage to fallback to.
@@ -62,6 +62,6 @@ export function initStoreFromUrlOrLocalStorage(): Store {
const raw = decodeStore(encodedSource);
invariant(isValidStore(raw), "Invalid Store");
invariant(isValidStore(raw), 'Invalid Store');
return raw;
}

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const path = require("path");
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const path = require('path');
const nextConfig = {
reactStrictMode: true,
@@ -14,24 +14,24 @@ const nextConfig = {
// Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets.
config.module.rules.push({
test: /\.d\.ts/,
type: "asset/source",
type: 'asset/source',
});
// Monaco Editor
if (!options.isServer) {
config.plugins.push(
new MonacoWebpackPlugin({
languages: ["typescript", "javascript"],
filename: "static/[name].worker.js",
languages: ['typescript', 'javascript'],
filename: 'static/[name].worker.js',
})
);
}
config.resolve.alias = {
...config.resolve.alias,
"react-compiler-runtime": path.resolve(
'react-compiler-runtime': path.resolve(
__dirname,
"../../packages/react-compiler-runtime"
'../../packages/react-compiler-runtime'
),
};
config.resolve.fallback = {
@@ -43,7 +43,7 @@ const nextConfig = {
return config;
},
transpilePackages: ["monaco-editor"],
transpilePackages: ['monaco-editor'],
};
module.exports = nextConfig;

View File

@@ -33,7 +33,7 @@
"monaco-editor": "^0.34.1",
"next": "^13.5.6",
"notistack": "^3.0.0-alpha.7",
"prettier": "3.0.3",
"prettier": "^3.3.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "18.2.0",
@@ -42,7 +42,6 @@
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/prettier": "^2.7.1",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"autoprefixer": "^10.4.13",

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import { defineConfig, devices } from "@playwright/test";
import path from "path";
import {defineConfig, devices} from '@playwright/test';
import path from 'path';
// Use process.env.PORT by default and fallback to port 3000
const PORT = process.env.PORT || 3000;
@@ -19,19 +19,19 @@ export default defineConfig({
// Timeout per test
timeout: 30 * 1000,
// Test directory
testDir: path.join(__dirname, "__tests__/e2e"),
testDir: path.join(__dirname, '__tests__/e2e'),
// If a test fails, retry it additional 2 times
retries: 2,
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: "test-results/",
outputDir: 'test-results/',
// Note: we only use text snapshots, so its safe to omit the host environment name
snapshotPathTemplate: "{testDir}/__snapshots__/{testFilePath}/{arg}{ext}",
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
// Run your local dev server before starting the tests:
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
webServer: {
command:
"yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev",
'yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev',
url: baseURL,
timeout: 300 * 1000,
reuseExistingServer: !process.env.CI,
@@ -44,7 +44,7 @@ export default defineConfig({
// Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc.
// More information: https://playwright.dev/docs/trace-viewer
trace: "retry-with-trace",
trace: 'retry-with-trace',
// All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context
// contextOptions: {
@@ -54,8 +54,8 @@ export default defineConfig({
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
name: 'chromium',
use: {...devices['Desktop Chrome']},
},
// {
// name: 'Desktop Firefox',

View File

@@ -5,16 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
const { execSync } = require("child_process");
const {execSync} = require('child_process');
// So that we don't need to check them into the repo.
// See https://github.com/reactjs/reactjs.org/blob/main/beta/scripts/downloadFonts.js.
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2"
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2'
);
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2"
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2'
);
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2"
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2'
);

View File

@@ -5,33 +5,33 @@
* LICENSE file in the root directory of this source tree.
*/
const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("./colors");
const defaultTheme = require('tailwindcss/defaultTheme');
const colors = require('./colors');
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./lib/forgetMonacoDiagnostics.ts",
'./app/**/*.{js,ts,jsx,tsx}',
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./lib/forgetMonacoDiagnostics.ts',
],
theme: {
extend: {
colors,
width: {
toast: "min(900px, 100vw - 40px)",
"toast-body": "calc(100% - 60px)",
"toast-title": "calc(100% - 40px)",
toast: 'min(900px, 100vw - 40px)',
'toast-body': 'calc(100% - 60px)',
'toast-title': 'calc(100% - 40px)',
},
height: {
content: "calc(100vh - 45px)",
monaco: "calc(100vh - 93px)",
monaco_small: "calc(100vh - 129px)",
content: 'calc(100vh - 45px)',
monaco: 'calc(100vh - 93px)',
monaco_small: 'calc(100vh - 129px)',
},
fontFamily: {
sans: [
"Optimistic Display",
"-apple-system",
'Optimistic Display',
'-apple-system',
...defaultTheme.fontFamily.sans,
],
},

View File

@@ -12,7 +12,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/react-forget.git"
"url": "git+https://github.com/facebook/react.git"
},
"scripts": {
"copyright": "node scripts/copyright.js",
@@ -38,7 +38,8 @@
"concurrently": "^7.4.0",
"folder-hash": "^4.0.4",
"ora": "5.4.1",
"prettier": "^3.2.5",
"prettier": "^3.3.3",
"prettier-plugin-hermes-parser": "^0.23.0",
"prompt-promise": "^1.0.3",
"rollup": "^4.13.2",
"rollup-plugin-banner2": "^1.2.3",

View File

@@ -7,7 +7,7 @@
/** @type {import('jest').Config} */
const config = {
projects: ["<rootDir>/scripts/jest/*.config.js"],
projects: ['<rootDir>/scripts/jest/*.config.js'],
};
module.exports = config;

View File

@@ -1,6 +1,6 @@
{
"name": "babel-plugin-react-compiler",
"version": "0.0.0-experimental-696af53-20240625",
"version": "0.0.0-experimental-334f00b-20240725",
"description": "Babel plugin for React Compiler.",
"main": "dist/index.js",
"license": "MIT",
@@ -63,5 +63,10 @@
"@babel/core": "7.2.0",
"@babel/generator": "7.2.0",
"@babel/traverse": "7.7.4"
},
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/react.git",
"directory": "compiler/packages/babel-plugin-react-compiler"
}
}

View File

@@ -5,29 +5,29 @@
* LICENSE file in the root directory of this source tree.
*/
import typescript from "@rollup/plugin-typescript";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import path from "path";
import process from "process";
import terser from "@rollup/plugin-terser";
import prettier from "rollup-plugin-prettier";
import banner2 from "rollup-plugin-banner2";
import typescript from '@rollup/plugin-typescript';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import path from 'path';
import process from 'process';
import terser from '@rollup/plugin-terser';
import prettier from 'rollup-plugin-prettier';
import banner2 from 'rollup-plugin-banner2';
const NO_INLINE = new Set(["@babel/types"]);
const NO_INLINE = new Set(['@babel/types']);
const DEV_ROLLUP_CONFIG = {
input: "src/index.ts",
input: 'src/index.ts',
output: {
file: "dist/index.js",
format: "cjs",
file: 'dist/index.js',
format: 'cjs',
sourcemap: false,
exports: "named",
exports: 'named',
},
plugins: [
typescript({
tsconfig: "./tsconfig.json",
tsconfig: './tsconfig.json',
compilerOptions: {
noEmit: true,
},
@@ -35,8 +35,8 @@ const DEV_ROLLUP_CONFIG = {
json(),
nodeResolve({
preferBuiltins: true,
resolveOnly: (module) => NO_INLINE.has(module) === false,
rootDir: path.join(process.cwd(), ".."),
resolveOnly: module => NO_INLINE.has(module) === false,
rootDir: path.join(process.cwd(), '..'),
}),
commonjs(),
terser({

View File

@@ -5,15 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import type * as BabelCore from "@babel/core";
import { NodePath } from "@babel/core";
import * as t from "@babel/types";
import type * as BabelCore from '@babel/core';
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
export default function AnnotateReactCodeBabelPlugin(
_babel: typeof BabelCore
_babel: typeof BabelCore,
): BabelCore.PluginObj {
return {
name: "annotate-react-code",
name: 'annotate-react-code',
visitor: {
Program(prog): void {
annotate(prog);
@@ -56,23 +56,23 @@ function buildTypeOfReactForget(): t.Statement {
// typeof globalThis[Symbol.for("react_forget")]
return t.expressionStatement(
t.unaryExpression(
"typeof",
'typeof',
t.memberExpression(
t.identifier("globalThis"),
t.identifier('globalThis'),
t.callExpression(
t.memberExpression(
t.identifier("Symbol"),
t.identifier("for"),
t.identifier('Symbol'),
t.identifier('for'),
false,
false,
false
),
[t.stringLiteral("react_forget")]
[t.stringLiteral('react_forget')],
),
true,
false
false,
),
true
)
true,
),
);
}
@@ -89,9 +89,9 @@ type BabelFn =
| NodePath<t.ArrowFunctionExpression>;
export function isComponentDeclaration(
node: t.FunctionDeclaration
node: t.FunctionDeclaration,
): node is ComponentDeclaration {
return Object.prototype.hasOwnProperty.call(node, "__componentDeclaration");
return Object.prototype.hasOwnProperty.call(node, '__componentDeclaration');
}
/*
@@ -101,7 +101,7 @@ export function isComponentDeclaration(
function isComponentOrHookLike(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
): boolean {
const functionName = getFunctionName(node);
// Check if the name is component or hook like:
@@ -114,7 +114,7 @@ function isComponentOrHookLike(
* helpers are _usually_ named with lowercase, but some code may
* violate this rule
*/
node.get("params").length <= 1
node.get('params').length <= 1
);
} else if (functionName !== null && isHook(functionName)) {
// Hooks have hook invocations or JSX, but can take any # of arguments
@@ -151,11 +151,11 @@ function isHook(path: NodePath<t.Expression | t.PrivateName>): boolean {
} else if (
path.isMemberExpression() &&
!path.node.computed &&
isHook(path.get("property"))
isHook(path.get('property'))
) {
const obj = path.get("object").node;
const obj = path.get('object').node;
const isPascalCaseNameSpace = /^[A-Z].*/;
return obj.type === "Identifier" && isPascalCaseNameSpace.test(obj.name);
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
} else {
return false;
}
@@ -177,8 +177,8 @@ function isComponentName(path: NodePath<t.Expression>): boolean {
function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
return !!(
path.parentPath.isCallExpression() &&
path.parentPath.get("callee").isExpression() &&
isReactAPI(path.parentPath.get("callee"), "forwardRef")
path.parentPath.get('callee').isExpression() &&
isReactAPI(path.parentPath.get('callee'), 'forwardRef')
);
}
@@ -190,22 +190,22 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
function isMemoCallback(path: NodePath<t.Expression>): boolean {
return (
path.parentPath.isCallExpression() &&
path.parentPath.get("callee").isExpression() &&
isReactAPI(path.parentPath.get("callee"), "memo")
path.parentPath.get('callee').isExpression() &&
isReactAPI(path.parentPath.get('callee'), 'memo')
);
}
function isReactAPI(
path: NodePath<t.Expression | t.PrivateName | t.V8IntrinsicIdentifier>,
functionName: string
functionName: string,
): boolean {
const node = path.node;
return (
(node.type === "Identifier" && node.name === functionName) ||
(node.type === "MemberExpression" &&
node.object.type === "Identifier" &&
node.object.name === "React" &&
node.property.type === "Identifier" &&
(node.type === 'Identifier' && node.name === functionName) ||
(node.type === 'MemberExpression' &&
node.object.type === 'Identifier' &&
node.object.name === 'React' &&
node.property.type === 'Identifier' &&
node.property.name === functionName)
);
}
@@ -218,7 +218,7 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
createsJsx = true;
},
CallExpression(call) {
const callee = call.get("callee");
const callee = call.get('callee');
if (callee.isExpression() && isHook(callee)) {
invokesHooks = true;
}
@@ -239,10 +239,10 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
function getFunctionName(
path: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
): NodePath<t.Expression> | null {
if (path.isFunctionDeclaration()) {
const id = path.get("id");
const id = path.get('id');
if (id.isIdentifier()) {
return id;
}
@@ -250,31 +250,31 @@ function getFunctionName(
}
let id: NodePath<t.LVal | t.Expression | t.PrivateName> | null = null;
const parent = path.parentPath;
if (parent.isVariableDeclarator() && parent.get("init").node === path.node) {
if (parent.isVariableDeclarator() && parent.get('init').node === path.node) {
// const useHook = () => {};
id = parent.get("id");
id = parent.get('id');
} else if (
parent.isAssignmentExpression() &&
parent.get("right").node === path.node &&
parent.get("operator") === "="
parent.get('right').node === path.node &&
parent.get('operator') === '='
) {
// useHook = () => {};
id = parent.get("left");
id = parent.get('left');
} else if (
parent.isProperty() &&
parent.get("value").node === path.node &&
!parent.get("computed") &&
parent.get("key").isLVal()
parent.get('value').node === path.node &&
!parent.get('computed') &&
parent.get('key').isLVal()
) {
/*
* {useHook: () => {}}
* {useHook() {}}
*/
id = parent.get("key");
id = parent.get('key');
} else if (
parent.isAssignmentPattern() &&
parent.get("right").node === path.node &&
!parent.get("computed")
parent.get('right').node === path.node &&
!parent.get('computed')
) {
/*
* const {useHook = () => {}} = {};
@@ -283,7 +283,7 @@ function getFunctionName(
* Kinda clowny, but we'd said we'd follow spec convention for
* `IsAnonymousFunctionDefinition()` usage.
*/
id = parent.get("left");
id = parent.get('left');
}
if (id !== null && (id.isIdentifier() || id.isMemberExpression())) {
return id;

View File

@@ -5,27 +5,27 @@
* LICENSE file in the root directory of this source tree.
*/
"use strict";
'use strict';
const { tests } = require("./eslint-plugin-react-hooks-test-cases");
const {tests} = require('./eslint-plugin-react-hooks-test-cases');
const {
runBabelPluginReactCompiler,
} = require("../dist/Babel/RunReactCompilerBabelPlugin");
const fs = require("fs");
const path = require("path");
const prettier = require("prettier");
const prettierConfigPath = require.resolve("../.prettierrc");
const process = require("process");
const { createHash } = require("crypto");
const { create } = require("domain");
} = require('../dist/Babel/RunReactCompilerBabelPlugin');
const fs = require('fs');
const path = require('path');
const prettier = require('prettier');
const prettierConfigPath = require.resolve('../.prettierrc');
const process = require('process');
const {createHash} = require('crypto');
const {create} = require('domain');
const FIXTURES_DIR = path.join(
process.cwd(),
"src",
"__tests__",
"fixtures",
"compiler",
"rules-of-hooks"
'src',
'__tests__',
'fixtures',
'compiler',
'rules-of-hooks'
);
const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
@@ -34,10 +34,10 @@ const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
const fixtures = [];
for (const test of tests.valid) {
fixtures.push({ code: test.code, valid: true });
fixtures.push({code: test.code, valid: true});
}
for (const test of tests.invalid) {
fixtures.push({ code: test.code, valid: false });
fixtures.push({code: test.code, valid: false});
}
for (const fixture of fixtures) {
@@ -47,8 +47,8 @@ for (const fixture of fixtures) {
// Does the fixture pass with hooks validation disabled? if not skip it
runBabelPluginReactCompiler(
fixture.code,
"rules-of-hooks.js",
"typescript",
'rules-of-hooks.js',
'typescript',
{
environment: {
validateHooksUsage: false,
@@ -59,8 +59,8 @@ for (const fixture of fixtures) {
try {
runBabelPluginReactCompiler(
fixture.code,
"rules-of-hooks.js",
"typescript",
'rules-of-hooks.js',
'typescript',
{
environment: {
validateHooksUsage: true,
@@ -74,7 +74,7 @@ for (const fixture of fixtures) {
error = e;
}
let code = fixture.code;
let prefix = "";
let prefix = '';
if (error !== null) {
prefix = `todo.bail.`;
code = `// @skip\n// Unsupported input\n${code}`;
@@ -92,11 +92,11 @@ for (const fixture of fixtures) {
code = `// @skip\n// Failed but should have passed\n${code}`;
}
const formatted = prettier.format(code, PRETTIER_OPTIONS);
const hmac = createHash("sha256");
hmac.update(formatted, "utf8");
const hmac = createHash('sha256');
hmac.update(formatted, 'utf8');
let name = `${prefix}rules-of-hooks-${hmac
.digest("hex")
.digest('hex')
.substring(0, 12)}.js`;
const fixturePath = path.join(FIXTURES_DIR, name);
fs.writeFileSync(fixturePath, formatted, "utf8");
fs.writeFileSync(fixturePath, formatted, 'utf8');
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
"use strict";
'use strict';
// NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
@@ -13,9 +13,9 @@
* A string template tag that removes padding from the left side of multi-line strings
*/
function normalizeIndent(strings) {
const codeLines = strings[0].split("\n");
const codeLines = strings[0].split('\n');
const leftPadding = codeLines[1].match(/\s+/)[0];
return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
}
module.exports.tests = {

View File

@@ -5,6 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
const makeE2EConfig = require("../jest/makeE2EConfig");
const makeE2EConfig = require('../jest/makeE2EConfig');
module.exports = makeE2EConfig("e2e no forget", false);
module.exports = makeE2EConfig('e2e no forget', false);

View File

@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
const makeE2EConfig = require("../jest/makeE2EConfig");
const makeE2EConfig = require('../jest/makeE2EConfig');
const config = makeE2EConfig("e2e with forget", true);
config.setupFilesAfterEnv = ["<rootDir>/../scripts/jest/setupEnvE2E.js"];
const config = makeE2EConfig('e2e with forget', true);
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
module.exports = config;

View File

@@ -6,10 +6,10 @@
*/
module.exports = {
displayName: "main",
preset: "ts-jest",
rootDir: "../../src",
testPathIgnorePatterns: ["e2e", "TestDriver", "test-utils", "fixtures"],
displayName: 'main',
preset: 'ts-jest',
rootDir: '../../src',
testPathIgnorePatterns: ['e2e', 'TestDriver', 'test-utils', 'fixtures'],
globals: {
__DEV__: true,
},

View File

@@ -8,27 +8,27 @@
module.exports = function makeE2EConfig(displayName, useForget) {
return {
displayName,
testEnvironment: "jsdom",
rootDir: "../../src",
testMatch: ["**/*.e2e.(js|tsx)"],
testEnvironment: 'jsdom',
rootDir: '../../src',
testMatch: ['**/*.e2e.(js|tsx)'],
modulePathIgnorePatterns: [
// ignore snapshots from the opposite forget configuration
useForget ? ".*\\.no-forget\\.snap$" : ".*\\.with-forget\\.snap$",
useForget ? '.*\\.no-forget\\.snap$' : '.*\\.with-forget\\.snap$',
// ignore snapshots from the main project
".*\\.ts\\.snap$",
'.*\\.ts\\.snap$',
],
globals: {
__FORGET__: useForget,
},
snapshotResolver: useForget
? "<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js"
: "<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js",
? '<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js'
: '<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js',
transform: {
"\\.[tj]sx?$": useForget
? "<rootDir>/../scripts/jest/transform-with-forget"
: "<rootDir>/../scripts/jest/transform-no-forget",
'\\.[tj]sx?$': useForget
? '<rootDir>/../scripts/jest/transform-with-forget'
: '<rootDir>/../scripts/jest/transform-no-forget',
},
transformIgnorePatterns: ["/node_modules/"],
transformIgnorePatterns: ['/node_modules/'],
};
};

View File

@@ -6,7 +6,7 @@
*/
module.exports = function makeSnapshotResolver(useForget) {
const modeExtension = useForget ? ".with-forget" : ".no-forget";
const modeExtension = useForget ? '.with-forget' : '.no-forget';
return {
resolveSnapshotPath: (testPath, snapshotExtension) =>
testPath + modeExtension + snapshotExtension,
@@ -17,6 +17,6 @@ module.exports = function makeSnapshotResolver(useForget) {
-modeExtension.length - snapshotExtension.length
),
testPathForConsistencyCheck: "some/__tests__/example.test.js",
testPathForConsistencyCheck: 'some/__tests__/example.test.js',
};
};

View File

@@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
import { jsx } from "@babel/plugin-syntax-jsx";
import babelJest from "babel-jest";
import { compile } from "babel-plugin-react-compiler";
import { execSync } from "child_process";
import {jsx} from '@babel/plugin-syntax-jsx';
import babelJest from 'babel-jest';
import {compile} from 'babel-plugin-react-compiler';
import {execSync} from 'child_process';
import type { NodePath, Visitor } from "@babel/traverse";
import type { CallExpression, FunctionDeclaration } from "@babel/types";
import * as t from "@babel/types";
import type {NodePath, Visitor} from '@babel/traverse';
import type {CallExpression, FunctionDeclaration} from '@babel/types';
import * as t from '@babel/types';
import {
EnvironmentConfig,
validateEnvironmentConfig,
} from "babel-plugin-react-compiler";
import { basename } from "path";
} from 'babel-plugin-react-compiler';
import {basename} from 'path';
/**
* -- IMPORTANT --
@@ -30,14 +30,14 @@ const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
enableAssumeHooksFollowRulesOfReact: true,
enableFunctionOutlining: false,
});
const debugMode = process.env["DEBUG_FORGET_COMPILER"] != null;
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
module.exports = (useForget: boolean) => {
function createTransformer() {
return babelJest.createTransformer({
passPerPreset: true,
presets: [
"@babel/preset-typescript",
'@babel/preset-typescript',
{
plugins: [
useForget
@@ -49,36 +49,36 @@ module.exports = (useForget: boolean) => {
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
*/
compilerCacheKey: execSync(
"yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist"
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
).toString(),
transformOptionsCacheKey: forgetOptions,
e2eTransformerCacheKey,
},
]
: "@babel/plugin-syntax-jsx",
: '@babel/plugin-syntax-jsx',
],
},
"@babel/preset-react",
'@babel/preset-react',
{
plugins: [
[
function BabelPluginRewriteRequirePath(): { visitor: Visitor } {
function BabelPluginRewriteRequirePath(): {visitor: Visitor} {
return {
visitor: {
CallExpression(path: NodePath<CallExpression>): void {
const { callee } = path.node;
const {callee} = path.node;
if (
callee.type === "Identifier" &&
callee.name === "require"
callee.type === 'Identifier' &&
callee.name === 'require'
) {
const arg = path.node.arguments[0];
if (arg.type === "StringLiteral") {
if (arg.type === 'StringLiteral') {
/*
* The compiler adds requires of "React", which is expected to be a wrapper
* around the "react" package. For tests, we just rewrite the require.
*/
if (arg.value === "React") {
arg.value = "react";
if (arg.value === 'React') {
arg.value = 'react';
}
}
}
@@ -87,7 +87,7 @@ module.exports = (useForget: boolean) => {
};
},
],
"@babel/plugin-transform-modules-commonjs",
'@babel/plugin-transform-modules-commonjs',
],
},
],
@@ -125,7 +125,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
fn.traverse({
DirectiveLiteral(path) {
if (path.node.value === "use no forget") {
if (path.node.value === 'use no forget') {
hasNoUseForgetDirective = true;
}
},
@@ -140,7 +140,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
CallExpression(path) {
// Is there hook usage?
if (
path.node.callee.type === "Identifier" &&
path.node.callee.type === 'Identifier' &&
!/^use[A-Z0-9]/.test(path.node.callee.name)
) {
isReactComponent = true;
@@ -170,7 +170,7 @@ function ReactForgetFunctionTransform() {
const filename = basename(state.file.opts.filename);
if (fn.node.loc && fn.node.id) {
console.log(
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
);
} else {
console.log(` Compiling ${filename} ${fn.node.id?.name}`);
@@ -180,11 +180,11 @@ function ReactForgetFunctionTransform() {
const compiled = compile(
fn,
forgetOptions,
"Other",
"_c",
'Other',
'_c',
null,
null,
null,
null
);
compiledFns.add(compiled);
@@ -193,14 +193,14 @@ function ReactForgetFunctionTransform() {
compiled.params,
compiled.body,
compiled.generator,
compiled.async
compiled.async,
);
fn.replaceWith(fun);
fn.skip();
},
};
return {
name: "react-forget-e2e",
name: 'react-forget-e2e',
inherits: jsx,
visitor,
};

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
const ReactCompilerRuntime = require("react/compiler-runtime");
const ReactCompilerRuntime = require('react/compiler-runtime');
/*
* Our e2e babel transform currently only compiles functions, not programs.

View File

@@ -5,6 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
const makeSnapshotResolver = require("./makeSnapshotResolver");
const makeSnapshotResolver = require('./makeSnapshotResolver');
module.exports = makeSnapshotResolver(false);

View File

@@ -5,6 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
const makeSnapshotResolver = require("./makeSnapshotResolver");
const makeSnapshotResolver = require('./makeSnapshotResolver');
module.exports = makeSnapshotResolver(true);

View File

@@ -5,4 +5,4 @@
* LICENSE file in the root directory of this source tree.
*/
module.exports = require("./makeTransform")(false);
module.exports = require('./makeTransform')(false);

View File

@@ -5,4 +5,4 @@
* LICENSE file in the root directory of this source tree.
*/
module.exports = require("./makeTransform")(true);
module.exports = require('./makeTransform')(true);

View File

@@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import type * as BabelCore from "@babel/core";
import { compileProgram, parsePluginOptions } from "../Entrypoint";
import type * as BabelCore from '@babel/core';
import {compileProgram, parsePluginOptions} from '../Entrypoint';
import {
injectReanimatedFlag,
pipelineUsesReanimatedPlugin,
} from "../Entrypoint/Reanimated";
} from '../Entrypoint/Reanimated';
/*
* The React Forget Babel Plugin
@@ -18,10 +18,10 @@ import {
* @returns
*/
export default function BabelPluginReactCompiler(
_babel: typeof BabelCore
_babel: typeof BabelCore,
): BabelCore.PluginObj {
return {
name: "react-forget",
name: 'react-forget',
visitor: {
/*
* Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
@@ -31,8 +31,8 @@ export default function BabelPluginReactCompiler(
Program(prog, pass): void {
let opts = parsePluginOptions(pass.opts);
const isDev =
(typeof __DEV__ !== "undefined" && __DEV__ === true) ||
process.env["NODE_ENV"] === "development";
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
process.env['NODE_ENV'] === 'development';
if (
opts.enableReanimatedCheck === true &&
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)

View File

@@ -5,25 +5,25 @@
* LICENSE file in the root directory of this source tree.
*/
import type * as BabelCore from "@babel/core";
import { transformFromAstSync } from "@babel/core";
import * as BabelParser from "@babel/parser";
import invariant from "invariant";
import type { PluginOptions } from "../Entrypoint";
import BabelPluginReactCompiler from "./BabelPlugin";
import type * as BabelCore from '@babel/core';
import {transformFromAstSync} from '@babel/core';
import * as BabelParser from '@babel/parser';
import invariant from 'invariant';
import type {PluginOptions} from '../Entrypoint';
import BabelPluginReactCompiler from './BabelPlugin';
export const DEFAULT_PLUGINS = ["babel-plugin-fbt", "babel-plugin-fbt-runtime"];
export const DEFAULT_PLUGINS = ['babel-plugin-fbt', 'babel-plugin-fbt-runtime'];
export function runBabelPluginReactCompiler(
text: string,
file: string,
language: "flow" | "typescript",
language: 'flow' | 'typescript',
options: Partial<PluginOptions> | null,
includeAst: boolean = false
includeAst: boolean = false,
): BabelCore.BabelFileResult {
const ast = BabelParser.parse(text, {
sourceFilename: file,
plugins: [language, "jsx"],
sourceType: "module",
plugins: [language, 'jsx'],
sourceType: 'module',
});
const result = transformFromAstSync(ast, text, {
ast: includeAst,
@@ -32,16 +32,16 @@ export function runBabelPluginReactCompiler(
retainLines: true,
plugins: [
[BabelPluginReactCompiler, options],
"babel-plugin-fbt",
"babel-plugin-fbt-runtime",
'babel-plugin-fbt',
'babel-plugin-fbt-runtime',
],
sourceType: "module",
sourceType: 'module',
configFile: false,
babelrc: false,
});
invariant(
result?.code != null,
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`,
);
return result;
}

View File

@@ -5,37 +5,37 @@
* LICENSE file in the root directory of this source tree.
*/
import type { SourceLocation } from "./HIR";
import { assertExhaustive } from "./Utils/utils";
import type {SourceLocation} from './HIR';
import {assertExhaustive} from './Utils/utils';
export enum ErrorSeverity {
/**
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
* misunderstanding on the users part.
*/
InvalidJS = "InvalidJS",
InvalidJS = 'InvalidJS',
/**
* Code that breaks the rules of React.
*/
InvalidReact = "InvalidReact",
InvalidReact = 'InvalidReact',
/**
* Incorrect configuration of the compiler.
*/
InvalidConfig = "InvalidConfig",
InvalidConfig = 'InvalidConfig',
/**
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
* memoization.
*/
CannotPreserveMemoization = "CannotPreserveMemoization",
CannotPreserveMemoization = 'CannotPreserveMemoization',
/**
* Unhandled syntax that we don't support yet.
*/
Todo = "Todo",
Todo = 'Todo',
/**
* An unexpected internal error in the compiler that indicates critical issues that can panic
* the compiler.
*/
Invariant = "Invariant",
Invariant = 'Invariant',
}
export enum CompilerSuggestionOperation {
@@ -79,19 +79,19 @@ export class CompilerErrorDetail {
this.options = options;
}
get reason(): CompilerErrorDetailOptions["reason"] {
get reason(): CompilerErrorDetailOptions['reason'] {
return this.options.reason;
}
get description(): CompilerErrorDetailOptions["description"] {
get description(): CompilerErrorDetailOptions['description'] {
return this.options.description;
}
get severity(): CompilerErrorDetailOptions["severity"] {
get severity(): CompilerErrorDetailOptions['severity'] {
return this.options.severity;
}
get loc(): CompilerErrorDetailOptions["loc"] {
get loc(): CompilerErrorDetailOptions['loc'] {
return this.options.loc;
}
get suggestions(): CompilerErrorDetailOptions["suggestions"] {
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
return this.options.suggestions;
}
@@ -100,10 +100,10 @@ export class CompilerErrorDetail {
if (this.description != null) {
buffer.push(`. ${this.description}`);
}
if (this.loc != null && typeof this.loc !== "symbol") {
if (this.loc != null && typeof this.loc !== 'symbol') {
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
}
return buffer.join("");
return buffer.join('');
}
toString(): string {
@@ -116,7 +116,7 @@ export class CompilerError extends Error {
static invariant(
condition: unknown,
options: Omit<CompilerErrorDetailOptions, "severity">
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): asserts condition {
if (!condition) {
const errors = new CompilerError();
@@ -124,57 +124,57 @@ export class CompilerError extends Error {
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.Invariant,
})
}),
);
throw errors;
}
}
static throwTodo(
options: Omit<CompilerErrorDetailOptions, "severity">
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}),
);
throw errors;
}
static throwInvalidJS(
options: Omit<CompilerErrorDetailOptions, "severity">
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidJS,
})
}),
);
throw errors;
}
static throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, "severity">
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidReact,
})
}),
);
throw errors;
}
static throwInvalidConfig(
options: Omit<CompilerErrorDetailOptions, "severity">
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
const errors = new CompilerError();
errors.pushErrorDetail(
new CompilerErrorDetail({
...options,
severity: ErrorSeverity.InvalidConfig,
})
}),
);
throw errors;
}
@@ -187,7 +187,7 @@ export class CompilerError extends Error {
constructor(...args: Array<any>) {
super(...args);
this.name = "ReactCompilerError";
this.name = 'ReactCompilerError';
}
override get message(): string {
@@ -197,7 +197,7 @@ export class CompilerError extends Error {
override set message(_message: string) {}
override toString(): string {
return this.details.map((detail) => detail.toString()).join("\n\n");
return this.details.map(detail => detail.toString()).join('\n\n');
}
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
@@ -206,7 +206,7 @@ export class CompilerError extends Error {
description: options.description ?? null,
severity: options.severity,
suggestions: options.suggestions,
loc: typeof options.loc === "symbol" ? null : options.loc,
loc: typeof options.loc === 'symbol' ? null : options.loc,
});
return this.pushErrorDetail(detail);
}
@@ -226,7 +226,7 @@ export class CompilerError extends Error {
* but otherwise continue compiling the rest of the app.
*/
isCritical(): boolean {
return this.details.some((detail) => {
return this.details.some(detail => {
switch (detail.severity) {
case ErrorSeverity.Invariant:
case ErrorSeverity.InvalidJS:
@@ -237,7 +237,7 @@ export class CompilerError extends Error {
case ErrorSeverity.Todo:
return false;
default:
assertExhaustive(detail.severity, "Unhandled error severity");
assertExhaustive(detail.severity, 'Unhandled error severity');
}
});
}

View File

@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import { NodePath } from "@babel/core";
import * as t from "@babel/types";
import { PluginOptions } from "./Options";
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {PluginOptions} from './Options';
export function insertGatedFunctionDeclaration(
fnPath: NodePath<
@@ -17,12 +17,12 @@ export function insertGatedFunctionDeclaration(
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
gating: NonNullable<PluginOptions["gating"]>
gating: NonNullable<PluginOptions['gating']>,
): void {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node)
buildFunctionExpression(fnPath.node),
);
/*
@@ -32,30 +32,30 @@ export function insertGatedFunctionDeclaration(
* conditional expression
*/
if (
fnPath.parentPath.node.type !== "ExportDefaultDeclaration" &&
fnPath.node.type === "FunctionDeclaration" &&
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration("const", [
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
])
]),
);
} else if (
fnPath.parentPath.node.type === "ExportDefaultDeclaration" &&
fnPath.node.type !== "ArrowFunctionExpression" &&
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name))
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration("const", [
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression
gatingExpression,
),
])
]),
);
} else {
fnPath.replaceWith(gatingExpression);
@@ -63,16 +63,19 @@ export function insertGatedFunctionDeclaration(
}
function buildFunctionExpression(
node: t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
node:
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
): t.ArrowFunctionExpression | t.FunctionExpression {
if (
node.type === "ArrowFunctionExpression" ||
node.type === "FunctionExpression"
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionExpression'
) {
return node;
} else {
const fn: t.FunctionExpression = {
type: "FunctionExpression",
type: 'FunctionExpression',
async: node.async,
generator: node.generator,
loc: node.loc ?? null,

View File

@@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
import { NodePath } from "@babel/core";
import * as t from "@babel/types";
import { CompilerError } from "../CompilerError";
import { ExternalFunction, GeneratedSource } from "../HIR";
import { getOrInsertDefault } from "../Utils/utils";
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {ExternalFunction, GeneratedSource} from '../HIR';
import {getOrInsertDefault} from '../Utils/utils';
export function addImportsToProgram(
path: NodePath<t.Program>,
importList: Array<ExternalFunction>
importList: Array<ExternalFunction>,
): void {
const identifiers: Set<string> = new Set();
const sortedImports: Map<string, Array<string>> = new Map();
for (const { importSpecifierName, source } of importList) {
for (const {importSpecifierName, source} of importList) {
/*
* Codegen currently does not rename import specifiers, so we do additional
* validation here
@@ -35,28 +35,28 @@ export function addImportsToProgram(
description: null,
loc: GeneratedSource,
suggestions: null,
}
},
);
identifiers.add(importSpecifierName);
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[]
[],
);
importSpecifierNameList.push(importSpecifierName);
}
const stmts: Array<t.ImportDeclaration> = [];
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map((name) => {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
});
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
}
path.unshiftContainer("body", stmts);
path.unshiftContainer('body', stmts);
}
/*
@@ -65,21 +65,21 @@ export function addImportsToProgram(
*/
function isNonNamespacedImport(
importDeclPath: NodePath<t.ImportDeclaration>,
moduleName: string
moduleName: string,
): boolean {
return (
importDeclPath.get("source").node.value === moduleName &&
importDeclPath.get('source').node.value === moduleName &&
importDeclPath
.get("specifiers")
.every((specifier) => specifier.isImportSpecifier()) &&
importDeclPath.node.importKind !== "type" &&
importDeclPath.node.importKind !== "typeof"
.get('specifiers')
.every(specifier => specifier.isImportSpecifier()) &&
importDeclPath.node.importKind !== 'type' &&
importDeclPath.node.importKind !== 'typeof'
);
}
function hasExistingNonNamespacedImportOfModule(
program: NodePath<t.Program>,
moduleName: string
moduleName: string,
): boolean {
let hasExistingImport = false;
program.traverse({
@@ -100,7 +100,7 @@ function hasExistingNonNamespacedImportOfModule(
function addMemoCacheFunctionSpecifierToExistingImport(
program: NodePath<t.Program>,
moduleName: string,
identifierName: string
identifierName: string,
): boolean {
let didInsertUseMemoCache = false;
program.traverse({
@@ -110,8 +110,8 @@ function addMemoCacheFunctionSpecifierToExistingImport(
isNonNamespacedImport(importDeclPath, moduleName)
) {
importDeclPath.pushContainer(
"specifiers",
t.importSpecifier(t.identifier(identifierName), t.identifier("c"))
'specifiers',
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
);
didInsertUseMemoCache = true;
}
@@ -123,7 +123,7 @@ function addMemoCacheFunctionSpecifierToExistingImport(
export function updateMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string,
useMemoCacheIdentifier: string
useMemoCacheIdentifier: string,
): void {
/*
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
@@ -131,25 +131,25 @@ export function updateMemoCacheFunctionImport(
*/
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
program,
moduleName
moduleName,
);
if (hasExistingImport) {
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
program,
moduleName,
useMemoCacheIdentifier
useMemoCacheIdentifier,
);
if (!didUpdateImport) {
throw new Error(
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
);
}
} else {
addMemoCacheFunctionImportDeclaration(
program,
moduleName,
useMemoCacheIdentifier
useMemoCacheIdentifier,
);
}
}
@@ -157,13 +157,13 @@ export function updateMemoCacheFunctionImport(
function addMemoCacheFunctionImportDeclaration(
program: NodePath<t.Program>,
moduleName: string,
localName: string
localName: string,
): void {
program.unshiftContainer(
"body",
'body',
t.importDeclaration(
[t.importSpecifier(t.identifier(localName), t.identifier("c"))],
t.stringLiteral(moduleName)
)
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
t.stringLiteral(moduleName),
),
);
}

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import * as t from "@babel/types";
import { z } from "zod";
import { CompilerErrorDetailOptions } from "../CompilerError";
import { ExternalFunction, PartialEnvironmentConfig } from "../HIR/Environment";
import { hasOwnProperty } from "../Utils/utils";
import * as t from '@babel/types';
import {z} from 'zod';
import {CompilerErrorDetailOptions} from '../CompilerError';
import {ExternalFunction, PartialEnvironmentConfig} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
const PanicThresholdOptionsSchema = z.enum([
/*
@@ -18,15 +18,15 @@ const PanicThresholdOptionsSchema = z.enum([
* If Forget is invoked through `BabelPluginReactCompiler`, this will at the least
* skip Forget compilation for the rest of current file.
*/
"all_errors",
'all_errors',
/*
* Panic by throwing an exception only on critical or unrecognized errors.
* For all other errors, skip the erroring function without inserting
* a Forget-compiled version (i.e. same behavior as noEmit).
*/
"critical_errors",
'critical_errors',
// Never panic by throwing an exception.
"none",
'none',
]);
export type PanicThresholdOptions = z.infer<typeof PanicThresholdOptionsSchema>;
@@ -130,13 +130,13 @@ const CompilationModeSchema = z.enum([
* false positives, since compilation has a greater impact than linting.
* This is the default mode
*/
"infer",
'infer',
// Compile only components using Flow component syntax and hooks using hook syntax.
"syntax",
'syntax',
// Compile only functions which are explicitly annotated with "use forget"
"annotation",
'annotation',
// Compile all top-level functions
"all",
'all',
]);
export type CompilationMode = z.infer<typeof CompilationModeSchema>;
@@ -156,17 +156,17 @@ export type CompilationMode = z.infer<typeof CompilationModeSchema>;
*/
export type LoggerEvent =
| {
kind: "CompileError";
kind: 'CompileError';
fnLoc: t.SourceLocation | null;
detail: CompilerErrorDetailOptions;
}
| {
kind: "CompileDiagnostic";
kind: 'CompileDiagnostic';
fnLoc: t.SourceLocation | null;
detail: Omit<Omit<CompilerErrorDetailOptions, "severity">, "suggestions">;
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
}
| {
kind: "CompileSuccess";
kind: 'CompileSuccess';
fnLoc: t.SourceLocation | null;
fnName: string | null;
memoSlots: number;
@@ -176,7 +176,7 @@ export type LoggerEvent =
prunedMemoValues: number;
}
| {
kind: "PipelineError";
kind: 'PipelineError';
fnLoc: t.SourceLocation | null;
data: string;
};
@@ -186,8 +186,8 @@ export type Logger = {
};
export const defaultOptions: PluginOptions = {
compilationMode: "infer",
panicThreshold: "none",
compilationMode: 'infer',
panicThreshold: 'none',
environment: {},
logger: null,
gating: null,
@@ -196,19 +196,19 @@ export const defaultOptions: PluginOptions = {
eslintSuppressionRules: null,
flowSuppressions: false,
ignoreUseNoForget: false,
sources: (filename) => {
return filename.indexOf("node_modules") === -1;
sources: filename => {
return filename.indexOf('node_modules') === -1;
},
enableReanimatedCheck: true,
} as const;
export function parsePluginOptions(obj: unknown): PluginOptions {
if (obj == null || typeof obj !== "object") {
if (obj == null || typeof obj !== 'object') {
return defaultOptions;
}
const parsedOptions = Object.create(null);
for (let [key, value] of Object.entries(obj)) {
if (typeof value === "string") {
if (typeof value === 'string') {
// normalize string configs to be case insensitive
value = value.toLowerCase();
}
@@ -216,7 +216,7 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
parsedOptions[key] = value;
}
}
return { ...defaultOptions, ...parsedOptions };
return {...defaultOptions, ...parsedOptions};
}
function isCompilerFlag(s: string): s is keyof PluginOptions {

View File

@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import prettyFormat from "pretty-format";
import { Logger } from ".";
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import prettyFormat from 'pretty-format';
import {Logger} from '.';
import {
HIRFunction,
ReactiveFunction,
@@ -22,13 +22,13 @@ import {
mergeConsecutiveBlocks,
mergeOverlappingReactiveScopesHIR,
pruneUnusedLabelsHIR,
} from "../HIR";
} from '../HIR';
import {
Environment,
EnvironmentConfig,
ReactFunctionType,
} from "../HIR/Environment";
import { findContextIdentifiers } from "../HIR/FindContextIdentifiers";
} from '../HIR/Environment';
import {findContextIdentifiers} from '../HIR/FindContextIdentifiers';
import {
analyseFunctions,
dropManualMemoization,
@@ -36,13 +36,13 @@ import {
inferReactivePlaces,
inferReferenceEffects,
inlineImmediatelyInvokedFunctionExpressions,
} from "../Inference";
} from '../Inference';
import {
constantPropagation,
deadCodeElimination,
pruneMaybeThrows,
} from "../Optimization";
import { instructionReordering } from "../Optimization/InstructionReordering";
} from '../Optimization';
import {instructionReordering} from '../Optimization/InstructionReordering';
import {
CodegenFunction,
alignObjectMethodScopes,
@@ -69,23 +69,23 @@ import {
pruneUnusedLabels,
pruneUnusedScopes,
renameVariables,
} from "../ReactiveScopes";
import { alignMethodCallScopes } from "../ReactiveScopes/AlignMethodCallScopes";
import { alignReactiveScopesToBlockScopesHIR } from "../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR";
import { flattenReactiveLoopsHIR } from "../ReactiveScopes/FlattenReactiveLoopsHIR";
import { flattenScopesWithHooksOrUseHIR } from "../ReactiveScopes/FlattenScopesWithHooksOrUseHIR";
import { pruneAlwaysInvalidatingScopes } from "../ReactiveScopes/PruneAlwaysInvalidatingScopes";
import pruneInitializationDependencies from "../ReactiveScopes/PruneInitializationDependencies";
import { stabilizeBlockIds } from "../ReactiveScopes/StabilizeBlockIds";
import { eliminateRedundantPhi, enterSSA, leaveSSA } from "../SSA";
import { inferTypes } from "../TypeInference";
} from '../ReactiveScopes';
import {alignMethodCallScopes} from '../ReactiveScopes/AlignMethodCallScopes';
import {alignReactiveScopesToBlockScopesHIR} from '../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR';
import {flattenReactiveLoopsHIR} from '../ReactiveScopes/FlattenReactiveLoopsHIR';
import {flattenScopesWithHooksOrUseHIR} from '../ReactiveScopes/FlattenScopesWithHooksOrUseHIR';
import {pruneAlwaysInvalidatingScopes} from '../ReactiveScopes/PruneAlwaysInvalidatingScopes';
import pruneInitializationDependencies from '../ReactiveScopes/PruneInitializationDependencies';
import {stabilizeBlockIds} from '../ReactiveScopes/StabilizeBlockIds';
import {eliminateRedundantPhi, enterSSA, leaveSSA} from '../SSA';
import {inferTypes} from '../TypeInference';
import {
logCodegenFunction,
logDebug,
logHIRFunction,
logReactiveFunction,
} from "../Utils/logger";
import { assertExhaustive } from "../Utils/utils";
} from '../Utils/logger';
import {assertExhaustive} from '../Utils/utils';
import {
validateContextVariableLValues,
validateHooksUsage,
@@ -95,15 +95,15 @@ import {
validateNoSetStateInRender,
validatePreservedManualMemoization,
validateUseMemo,
} from "../Validation";
import { validateLocalsNotReassignedAfterRender } from "../Validation/ValidateLocalsNotReassignedAfterRender";
import { outlineFunctions } from "../Optimization/OutlineFunctions";
} from '../Validation';
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
import {outlineFunctions} from '../Optimization/OutlineFunctions';
export type CompilerPipelineValue =
| { kind: "ast"; name: string; value: CodegenFunction }
| { kind: "hir"; name: string; value: HIRFunction }
| { kind: "reactive"; name: string; value: ReactiveFunction }
| { kind: "debug"; name: string; value: string };
| {kind: 'ast'; name: string; value: CodegenFunction}
| {kind: 'hir'; name: string; value: HIRFunction}
| {kind: 'reactive'; name: string; value: ReactiveFunction}
| {kind: 'debug'; name: string; value: string};
export function* run(
func: NodePath<
@@ -114,7 +114,7 @@ export function* run(
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null
code: string | null,
): Generator<CompilerPipelineValue, CodegenFunction> {
const contextIdentifiers = findContextIdentifiers(func);
const env = new Environment(
@@ -125,11 +125,11 @@ export function* run(
logger,
filename,
code,
useMemoCacheIdentifier
useMemoCacheIdentifier,
);
yield {
kind: "debug",
name: "EnvironmentConfig",
kind: 'debug',
name: 'EnvironmentConfig',
value: prettyFormat(env.config),
};
const ast = yield* runWithEnvironment(func, env);
@@ -144,13 +144,13 @@ function* runWithEnvironment(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
env: Environment
env: Environment,
): Generator<CompilerPipelineValue, CodegenFunction> {
const hir = lower(func, env).unwrap();
yield log({ kind: "hir", name: "HIR", value: hir });
yield log({kind: 'hir', name: 'HIR', value: hir});
pruneMaybeThrows(hir);
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir);
@@ -161,35 +161,35 @@ function* runWithEnvironment(
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir);
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
yield log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
inlineImmediatelyInvokedFunctionExpressions(hir);
yield log({
kind: "hir",
name: "InlineImmediatelyInvokedFunctionExpressions",
kind: 'hir',
name: 'InlineImmediatelyInvokedFunctionExpressions',
value: hir,
});
mergeConsecutiveBlocks(hir);
yield log({ kind: "hir", name: "MergeConsecutiveBlocks", value: hir });
yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
assertConsistentIdentifiers(hir);
assertTerminalSuccessorsExist(hir);
enterSSA(hir);
yield log({ kind: "hir", name: "SSA", value: hir });
yield log({kind: 'hir', name: 'SSA', value: hir});
eliminateRedundantPhi(hir);
yield log({ kind: "hir", name: "EliminateRedundantPhi", value: hir });
yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
assertConsistentIdentifiers(hir);
constantPropagation(hir);
yield log({ kind: "hir", name: "ConstantPropagation", value: hir });
yield log({kind: 'hir', name: 'ConstantPropagation', value: hir});
inferTypes(hir);
yield log({ kind: "hir", name: "InferTypes", value: hir });
yield log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
@@ -200,27 +200,27 @@ function* runWithEnvironment(
}
analyseFunctions(hir);
yield log({ kind: "hir", name: "AnalyseFunctions", value: hir });
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
inferReferenceEffects(hir);
yield log({ kind: "hir", name: "InferReferenceEffects", value: hir });
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
validateLocalsNotReassignedAfterRender(hir);
// Note: Has to come after infer reference effects because "dead" code may still affect inference
deadCodeElimination(hir);
yield log({ kind: "hir", name: "DeadCodeElimination", value: hir });
yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
if (env.config.enableInstructionReordering) {
instructionReordering(hir);
yield log({ kind: "hir", name: "InstructionReordering", value: hir });
yield log({kind: 'hir', name: 'InstructionReordering', value: hir});
}
pruneMaybeThrows(hir);
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
inferMutableRanges(hir);
yield log({ kind: "hir", name: "InferMutableRanges", value: hir });
yield log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
@@ -235,67 +235,67 @@ function* runWithEnvironment(
}
inferReactivePlaces(hir);
yield log({ kind: "hir", name: "InferReactivePlaces", value: hir });
yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
leaveSSA(hir);
yield log({ kind: "hir", name: "LeaveSSA", value: hir });
yield log({kind: 'hir', name: 'LeaveSSA', value: hir});
inferReactiveScopeVariables(hir);
yield log({ kind: "hir", name: "InferReactiveScopeVariables", value: hir });
yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir);
yield log({ kind: "hir", name: "OutlineFunctions", value: hir });
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
}
alignMethodCallScopes(hir);
yield log({
kind: "hir",
name: "AlignMethodCallScopes",
kind: 'hir',
name: 'AlignMethodCallScopes',
value: hir,
});
alignObjectMethodScopes(hir);
yield log({
kind: "hir",
name: "AlignObjectMethodScopes",
kind: 'hir',
name: 'AlignObjectMethodScopes',
value: hir,
});
const fbtOperands = memoizeFbtOperandsInSameScope(hir);
yield log({
kind: "hir",
name: "MemoizeFbtAndMacroOperandsInSameScope",
kind: 'hir',
name: 'MemoizeFbtAndMacroOperandsInSameScope',
value: hir,
});
if (env.config.enableReactiveScopesInHIR) {
pruneUnusedLabelsHIR(hir);
yield log({
kind: "hir",
name: "PruneUnusedLabelsHIR",
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
alignReactiveScopesToBlockScopesHIR(hir);
yield log({
kind: "hir",
name: "AlignReactiveScopesToBlockScopesHIR",
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
mergeOverlappingReactiveScopesHIR(hir);
yield log({
kind: "hir",
name: "MergeOverlappingReactiveScopesHIR",
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
value: hir,
});
assertValidBlockNesting(hir);
buildReactiveScopeTerminalsHIR(hir);
yield log({
kind: "hir",
name: "BuildReactiveScopeTerminalsHIR",
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
value: hir,
});
@@ -303,15 +303,15 @@ function* runWithEnvironment(
flattenReactiveLoopsHIR(hir);
yield log({
kind: "hir",
name: "FlattenReactiveLoopsHIR",
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
flattenScopesWithHooksOrUseHIR(hir);
yield log({
kind: "hir",
name: "FlattenScopesWithHooksOrUseHIR",
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
value: hir,
});
assertTerminalSuccessorsExist(hir);
@@ -320,8 +320,8 @@ function* runWithEnvironment(
const reactiveFunction = buildReactiveFunction(hir);
yield log({
kind: "reactive",
name: "BuildReactiveFunction",
kind: 'reactive',
name: 'BuildReactiveFunction',
value: reactiveFunction,
});
@@ -329,44 +329,44 @@ function* runWithEnvironment(
pruneUnusedLabels(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneUnusedLabels",
kind: 'reactive',
name: 'PruneUnusedLabels',
value: reactiveFunction,
});
if (!env.config.enableReactiveScopesInHIR) {
alignReactiveScopesToBlockScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "AlignReactiveScopesToBlockScopes",
kind: 'reactive',
name: 'AlignReactiveScopesToBlockScopes',
value: reactiveFunction,
});
mergeOverlappingReactiveScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "MergeOverlappingReactiveScopes",
kind: 'reactive',
name: 'MergeOverlappingReactiveScopes',
value: reactiveFunction,
});
buildReactiveBlocks(reactiveFunction);
yield log({
kind: "reactive",
name: "BuildReactiveBlocks",
kind: 'reactive',
name: 'BuildReactiveBlocks',
value: reactiveFunction,
});
flattenReactiveLoops(reactiveFunction);
yield log({
kind: "reactive",
name: "FlattenReactiveLoops",
kind: 'reactive',
name: 'FlattenReactiveLoops',
value: reactiveFunction,
});
flattenScopesWithHooksOrUse(reactiveFunction);
yield log({
kind: "reactive",
name: "FlattenScopesWithHooks",
kind: 'reactive',
name: 'FlattenScopesWithHooks',
value: reactiveFunction,
});
}
@@ -375,101 +375,101 @@ function* runWithEnvironment(
propagateScopeDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PropagateScopeDependencies",
kind: 'reactive',
name: 'PropagateScopeDependencies',
value: reactiveFunction,
});
pruneNonEscapingScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonEscapingScopes",
kind: 'reactive',
name: 'PruneNonEscapingScopes',
value: reactiveFunction,
});
pruneNonReactiveDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonReactiveDependencies",
kind: 'reactive',
name: 'PruneNonReactiveDependencies',
value: reactiveFunction,
});
pruneUnusedScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneUnusedScopes",
kind: 'reactive',
name: 'PruneUnusedScopes',
value: reactiveFunction,
});
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
yield log({
kind: "reactive",
name: "MergeReactiveScopesThatInvalidateTogether",
kind: 'reactive',
name: 'MergeReactiveScopesThatInvalidateTogether',
value: reactiveFunction,
});
pruneAlwaysInvalidatingScopes(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneAlwaysInvalidatingScopes",
kind: 'reactive',
name: 'PruneAlwaysInvalidatingScopes',
value: reactiveFunction,
});
if (env.config.enableChangeDetectionForDebugging != null) {
pruneInitializationDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneInitializationDependencies",
kind: 'reactive',
name: 'PruneInitializationDependencies',
value: reactiveFunction,
});
}
propagateEarlyReturns(reactiveFunction);
yield log({
kind: "reactive",
name: "PropagateEarlyReturns",
kind: 'reactive',
name: 'PropagateEarlyReturns',
value: reactiveFunction,
});
promoteUsedTemporaries(reactiveFunction);
yield log({
kind: "reactive",
name: "PromoteUsedTemporaries",
kind: 'reactive',
name: 'PromoteUsedTemporaries',
value: reactiveFunction,
});
pruneUnusedLValues(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneUnusedLValues",
kind: 'reactive',
name: 'PruneUnusedLValues',
value: reactiveFunction,
});
extractScopeDeclarationsFromDestructuring(reactiveFunction);
yield log({
kind: "reactive",
name: "ExtractScopeDeclarationsFromDestructuring",
kind: 'reactive',
name: 'ExtractScopeDeclarationsFromDestructuring',
value: reactiveFunction,
});
stabilizeBlockIds(reactiveFunction);
yield log({
kind: "reactive",
name: "StabilizeBlockIds",
kind: 'reactive',
name: 'StabilizeBlockIds',
value: reactiveFunction,
});
const uniqueIdentifiers = renameVariables(reactiveFunction);
yield log({
kind: "reactive",
name: "RenameVariables",
kind: 'reactive',
name: 'RenameVariables',
value: reactiveFunction,
});
pruneHoistedContexts(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneHoistedContexts",
kind: 'reactive',
name: 'PruneHoistedContexts',
value: reactiveFunction,
});
@@ -488,9 +488,9 @@ function* runWithEnvironment(
uniqueIdentifiers,
fbtOperands,
}).unwrap();
yield log({ kind: "ast", name: "Codegen", value: ast });
yield log({kind: 'ast', name: 'Codegen', value: ast});
for (const outlined of ast.outlined) {
yield log({ kind: "ast", name: "Codegen (outlined)", value: outlined.fn });
yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
}
/**
@@ -499,7 +499,7 @@ function* runWithEnvironment(
* thrown by babel functions or other unexpected exceptions).
*/
if (env.config.throwUnknownException__testonly) {
throw new Error("unexpected error");
throw new Error('unexpected error');
}
return ast;
@@ -514,7 +514,7 @@ export function compileFn(
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null
code: string | null,
): CodegenFunction {
let generator = run(
func,
@@ -523,7 +523,7 @@ export function compileFn(
useMemoCacheIdentifier,
logger,
filename,
code
code,
);
while (true) {
const next = generator.next();
@@ -535,24 +535,24 @@ export function compileFn(
export function log(value: CompilerPipelineValue): CompilerPipelineValue {
switch (value.kind) {
case "ast": {
case 'ast': {
logCodegenFunction(value.name, value.value);
break;
}
case "hir": {
case 'hir': {
logHIRFunction(value.name, value.value);
break;
}
case "reactive": {
case 'reactive': {
logReactiveFunction(value.name, value.value);
break;
}
case "debug": {
case 'debug': {
logDebug(value.name, value.value);
break;
}
default: {
assertExhaustive(value, "Unexpected compilation kind");
assertExhaustive(value, 'Unexpected compilation kind');
}
}
return value;

View File

@@ -5,32 +5,32 @@
* LICENSE file in the root directory of this source tree.
*/
import { NodePath } from "@babel/core";
import * as t from "@babel/types";
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetail,
ErrorSeverity,
} from "../CompilerError";
} from '../CompilerError';
import {
ExternalFunction,
ReactFunctionType,
parseEnvironmentConfig,
tryParseExternalFunction,
} from "../HIR/Environment";
import { CodegenFunction } from "../ReactiveScopes";
import { isComponentDeclaration } from "../Utils/ComponentDeclaration";
import { isHookDeclaration } from "../Utils/HookDeclaration";
import { assertExhaustive } from "../Utils/utils";
import { insertGatedFunctionDeclaration } from "./Gating";
import { addImportsToProgram, updateMemoCacheFunctionImport } from "./Imports";
import { PluginOptions } from "./Options";
import { compileFn } from "./Pipeline";
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
import {isHookDeclaration} from '../Utils/HookDeclaration';
import {assertExhaustive} from '../Utils/utils';
import {insertGatedFunctionDeclaration} from './Gating';
import {addImportsToProgram, updateMemoCacheFunctionImport} from './Imports';
import {PluginOptions} from './Options';
import {compileFn} from './Pipeline';
import {
filterSuppressionsThatAffectFunction,
findProgramSuppressions,
suppressionsToCompilerError,
} from "./Suppression";
} from './Suppression';
export type CompilerPass = {
opts: PluginOptions;
@@ -40,11 +40,11 @@ export type CompilerPass = {
};
function findDirectiveEnablingMemoization(
directives: Array<t.Directive>
directives: Array<t.Directive>,
): t.Directive | null {
for (const directive of directives) {
const directiveValue = directive.value.value;
if (directiveValue === "use forget" || directiveValue === "use memo") {
if (directiveValue === 'use forget' || directiveValue === 'use memo') {
return directive;
}
}
@@ -53,13 +53,13 @@ function findDirectiveEnablingMemoization(
function findDirectiveDisablingMemoization(
directives: Array<t.Directive>,
options: PluginOptions
options: PluginOptions,
): t.Directive | null {
for (const directive of directives) {
const directiveValue = directive.value.value;
if (
(directiveValue === "use no forget" ||
directiveValue === "use no memo") &&
(directiveValue === 'use no forget' ||
directiveValue === 'use no memo') &&
!options.ignoreUseNoForget
) {
return directive;
@@ -75,7 +75,7 @@ function isCriticalError(err: unknown): boolean {
function isConfigError(err: unknown): boolean {
if (err instanceof CompilerError) {
return err.details.some(
(detail) => detail.severity === ErrorSeverity.InvalidConfig
detail => detail.severity === ErrorSeverity.InvalidConfig,
);
}
return false;
@@ -92,7 +92,7 @@ export type CompileResult = {
* functions which were outlined. Only original functions need to be gated
* if gating mode is enabled.
*/
kind: "original" | "outlined";
kind: 'original' | 'outlined';
originalFn: BabelFn;
compiledFn: CodegenFunction;
};
@@ -100,13 +100,13 @@ export type CompileResult = {
function handleError(
err: unknown,
pass: CompilerPass,
fnLoc: t.SourceLocation | null
fnLoc: t.SourceLocation | null,
): void {
if (pass.opts.logger) {
if (err instanceof CompilerError) {
for (const detail of err.details) {
pass.opts.logger.logEvent(pass.filename, {
kind: "CompileError",
kind: 'CompileError',
fnLoc,
detail: detail.options,
});
@@ -116,19 +116,19 @@ function handleError(
if (err instanceof Error) {
stringifiedError = err.stack ?? err.message;
} else {
stringifiedError = err?.toString() ?? "[ null ]";
stringifiedError = err?.toString() ?? '[ null ]';
}
pass.opts.logger.logEvent(pass.filename, {
kind: "PipelineError",
kind: 'PipelineError',
fnLoc,
data: stringifiedError,
});
}
}
if (
pass.opts.panicThreshold === "all_errors" ||
(pass.opts.panicThreshold === "critical_errors" && isCriticalError(err)) ||
pass.opts.panicThreshold === 'all_errors' ||
(pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) ||
isConfigError(err) // Always throws regardless of panic threshold
) {
throw err;
@@ -137,16 +137,16 @@ function handleError(
export function createNewFunctionNode(
originalFn: BabelFn,
compiledFn: CodegenFunction
compiledFn: CodegenFunction,
): t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression {
let transformedFn:
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression;
switch (originalFn.node.type) {
case "FunctionDeclaration": {
case 'FunctionDeclaration': {
const fn: t.FunctionDeclaration = {
type: "FunctionDeclaration",
type: 'FunctionDeclaration',
id: compiledFn.id,
loc: originalFn.node.loc ?? null,
async: compiledFn.async,
@@ -157,9 +157,9 @@ export function createNewFunctionNode(
transformedFn = fn;
break;
}
case "ArrowFunctionExpression": {
case 'ArrowFunctionExpression': {
const fn: t.ArrowFunctionExpression = {
type: "ArrowFunctionExpression",
type: 'ArrowFunctionExpression',
loc: originalFn.node.loc ?? null,
async: compiledFn.async,
generator: compiledFn.generator,
@@ -170,9 +170,9 @@ export function createNewFunctionNode(
transformedFn = fn;
break;
}
case "FunctionExpression": {
case 'FunctionExpression': {
const fn: t.FunctionExpression = {
type: "FunctionExpression",
type: 'FunctionExpression',
id: compiledFn.id,
loc: originalFn.node.loc ?? null,
async: compiledFn.async,
@@ -183,13 +183,66 @@ export function createNewFunctionNode(
transformedFn = fn;
break;
}
default: {
assertExhaustive(
originalFn.node,
`Creating unhandled function: ${originalFn.node}`,
);
}
}
// Avoid visiting the new transformed version
ALREADY_COMPILED.add(transformedFn);
return transformedFn;
}
function insertNewOutlinedFunctionNode(
program: NodePath<t.Program>,
originalFn: BabelFn,
compiledFn: CodegenFunction,
): NodePath<t.Function> {
switch (originalFn.type) {
case 'FunctionDeclaration': {
return originalFn.insertAfter(
createNewFunctionNode(originalFn, compiledFn),
)[0]!;
}
/**
* We can't just append the outlined function as a sibling of the original function if it is an
* (Arrow)FunctionExpression parented by a VariableDeclaration, as this would cause its parent
* to become a SequenceExpression instead which breaks a bunch of assumptions elsewhere in the
* plugin.
*
* To get around this, we always synthesize a new FunctionDeclaration for the outlined function
* and insert it as a true sibling to the original function.
*/
case 'ArrowFunctionExpression':
case 'FunctionExpression': {
const fn: t.FunctionDeclaration = {
type: 'FunctionDeclaration',
id: compiledFn.id,
loc: originalFn.node.loc ?? null,
async: compiledFn.async,
generator: compiledFn.generator,
params: compiledFn.params,
body: compiledFn.body,
};
const insertedFuncDecl = program.pushContainer('body', [fn])[0]!;
CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
reason: 'Expected inserted function declaration',
description: `Got: ${insertedFuncDecl}`,
loc: insertedFuncDecl.node?.loc ?? null,
});
return insertedFuncDecl;
}
default: {
assertExhaustive(
originalFn,
`Inserting unhandled function: ${originalFn}`,
);
}
}
}
/*
* This is a hack to work around what seems to be a Babel bug. Babel doesn't
* consistently respect the `skip()` function to avoid revisiting a node within
@@ -198,15 +251,15 @@ export function createNewFunctionNode(
const ALREADY_COMPILED: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
const DEFAULT_ESLINT_SUPPRESSIONS = [
"react-hooks/exhaustive-deps",
"react-hooks/rules-of-hooks",
'react-hooks/exhaustive-deps',
'react-hooks/rules-of-hooks',
];
function isFilePartOfSources(
sources: Array<string> | ((filename: string) => boolean),
filename: string
filename: string,
): boolean {
if (typeof sources === "function") {
if (typeof sources === 'function') {
return sources(filename);
}
@@ -221,7 +274,7 @@ function isFilePartOfSources(
export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass
pass: CompilerPass,
): void {
if (pass.opts.sources) {
if (pass.filename === null) {
@@ -233,7 +286,7 @@ export function compileProgram(
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
severity: ErrorSeverity.InvalidConfig,
loc: null,
})
}),
);
handleError(error, pass, null);
return;
@@ -253,8 +306,8 @@ export function compileProgram(
}
const environment = parseEnvironmentConfig(pass.opts.environment ?? {});
const useMemoCacheIdentifier = program.scope.generateUidIdentifier("c");
const moduleName = pass.opts.runtimeModule ?? "react/compiler-runtime";
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
if (hasMemoCacheFunctionImport(program, moduleName)) {
return;
}
@@ -267,12 +320,12 @@ export function compileProgram(
const suppressions = findProgramSuppressions(
pass.comments,
pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS,
pass.opts.flowSuppressions
pass.opts.flowSuppressions,
);
const lintError = suppressionsToCompilerError(suppressions);
let hasCriticalError = lintError != null;
const queue: Array<{
kind: "original" | "outlined";
kind: 'original' | 'outlined';
fn: BabelFn;
fnType: ReactFunctionType;
}> = [];
@@ -292,7 +345,7 @@ export function compileProgram(
ALREADY_COMPILED.add(fn.node);
fn.skip();
queue.push({ kind: "original", fn, fnType });
queue.push({kind: 'original', fn, fnType});
};
// Main traversal to compile with Forget
@@ -324,14 +377,14 @@ export function compileProgram(
},
{
...pass,
opts: { ...pass.opts, ...pass.opts },
opts: {...pass.opts, ...pass.opts},
filename: pass.filename ?? null,
}
},
);
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType
fnType: ReactFunctionType,
): null | CodegenFunction => {
if (lintError != null) {
/**
@@ -341,7 +394,7 @@ export function compileProgram(
*/
const suppressionsInFunction = filterSuppressionsThatAffectFunction(
suppressions,
fn
fn,
);
if (suppressionsInFunction.length > 0) {
handleError(lintError, pass, fn.node.loc ?? null);
@@ -357,7 +410,7 @@ export function compileProgram(
if (environment.isErr()) {
CompilerError.throwInvalidConfig({
reason:
"Error in validating environment config. This is an advanced setting and not meant to be used directly",
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
description: environment.unwrapErr().toString(),
suggestions: null,
loc: null,
@@ -372,10 +425,10 @@ export function compileProgram(
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code
pass.code,
);
pass.opts.logger?.logEvent(pass.filename, {
kind: "CompileSuccess",
kind: 'CompileSuccess',
fnLoc: fn.node.loc ?? null,
fnName: compiledFn.id?.name ?? null,
memoSlots: compiledFn.memoSlotsUsed,
@@ -404,12 +457,14 @@ export function compileProgram(
}
for (const outlined of compiled.outlined) {
CompilerError.invariant(outlined.fn.outlined.length === 0, {
reason: "Unexpected nested outlined functions",
reason: 'Unexpected nested outlined functions',
loc: outlined.fn.loc,
});
const fn = current.fn.insertAfter(
createNewFunctionNode(current.fn, outlined.fn)
)[0]!;
const fn = insertNewOutlinedFunctionNode(
program,
current.fn,
outlined.fn,
);
fn.skip();
ALREADY_COMPILED.add(fn.node);
if (outlined.type !== null) {
@@ -437,9 +492,9 @@ export function compileProgram(
if (pass.opts.gating != null) {
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
program,
compiledFns.map((result) => {
compiledFns.map(result => {
return result.originalFn;
})
}),
);
if (error) {
handleError(error, pass, null);
@@ -460,32 +515,32 @@ export function compileProgram(
pass.opts.environment?.enableEmitInstrumentForget;
if (enableEmitInstrumentForget != null) {
externalFunctions.push(
tryParseExternalFunction(enableEmitInstrumentForget.fn)
tryParseExternalFunction(enableEmitInstrumentForget.fn),
);
if (enableEmitInstrumentForget.gating != null) {
externalFunctions.push(
tryParseExternalFunction(enableEmitInstrumentForget.gating)
tryParseExternalFunction(enableEmitInstrumentForget.gating),
);
}
}
if (pass.opts.environment?.enableEmitFreeze != null) {
const enableEmitFreeze = tryParseExternalFunction(
pass.opts.environment.enableEmitFreeze
pass.opts.environment.enableEmitFreeze,
);
externalFunctions.push(enableEmitFreeze);
}
if (pass.opts.environment?.enableEmitHookGuards != null) {
const enableEmitHookGuards = tryParseExternalFunction(
pass.opts.environment.enableEmitHookGuards
pass.opts.environment.enableEmitHookGuards,
);
externalFunctions.push(enableEmitHookGuards);
}
if (pass.opts.environment?.enableChangeDetectionForDebugging != null) {
const enableChangeDetectionForDebugging = tryParseExternalFunction(
pass.opts.environment.enableChangeDetectionForDebugging
pass.opts.environment.enableChangeDetectionForDebugging,
);
externalFunctions.push(enableChangeDetectionForDebugging);
}
@@ -499,10 +554,10 @@ export function compileProgram(
* error elsewhere in the file, regardless of bailout mode.
*/
for (const result of compiledFns) {
const { kind, originalFn, compiledFn } = result;
const {kind, originalFn, compiledFn} = result;
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (gating != null && kind === "original") {
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
} else {
originalFn.replaceWith(transformedFn);
@@ -523,7 +578,7 @@ export function compileProgram(
updateMemoCacheFunctionImport(
program,
moduleName,
useMemoCacheIdentifier.name
useMemoCacheIdentifier.name,
);
}
addImportsToProgram(program, externalFunctions);
@@ -532,18 +587,18 @@ export function compileProgram(
function getReactFunctionType(
fn: BabelFn,
pass: CompilerPass
pass: CompilerPass,
): ReactFunctionType | null {
const hookPattern = pass.opts.environment?.hookPattern ?? null;
if (fn.node.body.type === "BlockStatement") {
if (fn.node.body.type === 'BlockStatement') {
// Opt-outs disable compilation regardless of mode
const useNoForget = findDirectiveDisablingMemoization(
fn.node.body.directives,
pass.opts
pass.opts,
);
if (useNoForget != null) {
pass.opts.logger?.logEvent(pass.filename, {
kind: "CompileError",
kind: 'CompileError',
fnLoc: fn.node.body.loc ?? null,
detail: {
severity: ErrorSeverity.Todo,
@@ -556,7 +611,7 @@ function getReactFunctionType(
}
// Otherwise opt-ins enable compilation regardless of mode
if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) {
return getComponentOrHookLike(fn, hookPattern) ?? "Other";
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
}
}
@@ -564,36 +619,36 @@ function getReactFunctionType(
let componentSyntaxType: ReactFunctionType | null = null;
if (fn.isFunctionDeclaration()) {
if (isComponentDeclaration(fn.node)) {
componentSyntaxType = "Component";
componentSyntaxType = 'Component';
} else if (isHookDeclaration(fn.node)) {
componentSyntaxType = "Hook";
componentSyntaxType = 'Hook';
}
}
switch (pass.opts.compilationMode) {
case "annotation": {
case 'annotation': {
// opt-ins are checked above
return null;
}
case "infer": {
case 'infer': {
// Check if this is a component or hook-like function
return componentSyntaxType ?? getComponentOrHookLike(fn, hookPattern);
}
case "syntax": {
case 'syntax': {
return componentSyntaxType;
}
case "all": {
case 'all': {
// Compile only top level functions
if (fn.scope.getProgramParent() !== fn.scope.parent) {
return null;
}
return getComponentOrHookLike(fn, hookPattern) ?? "Other";
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
}
default: {
assertExhaustive(
pass.opts.compilationMode,
`Unexpected compilationMode \`${pass.opts.compilationMode}\``
`Unexpected compilationMode \`${pass.opts.compilationMode}\``,
);
}
}
@@ -606,12 +661,12 @@ function getReactFunctionType(
*/
function hasMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string
moduleName: string,
): boolean {
let hasUseMemoCache = false;
program.traverse({
ImportSpecifier(path) {
const imported = path.get("imported");
const imported = path.get('imported');
let importedName: string | null = null;
if (imported.isIdentifier()) {
importedName = imported.node.name;
@@ -619,9 +674,9 @@ function hasMemoCacheFunctionImport(
importedName = imported.node.value;
}
if (
importedName === "c" &&
importedName === 'c' &&
path.parentPath.isImportDeclaration() &&
path.parentPath.get("source").node.value === moduleName
path.parentPath.get('source').node.value === moduleName
) {
hasUseMemoCache = true;
}
@@ -644,18 +699,18 @@ function isHookName(s: string, hookPattern: string | null): boolean {
function isHook(
path: NodePath<t.Expression | t.PrivateName>,
hookPattern: string | null
hookPattern: string | null,
): boolean {
if (path.isIdentifier()) {
return isHookName(path.node.name, hookPattern);
} else if (
path.isMemberExpression() &&
!path.node.computed &&
isHook(path.get("property"), hookPattern)
isHook(path.get('property'), hookPattern)
) {
const obj = path.get("object").node;
const obj = path.get('object').node;
const isPascalCaseNameSpace = /^[A-Z].*/;
return obj.type === "Identifier" && isPascalCaseNameSpace.test(obj.name);
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
} else {
return false;
}
@@ -672,15 +727,15 @@ function isComponentName(path: NodePath<t.Expression>): boolean {
function isReactAPI(
path: NodePath<t.Expression | t.PrivateName | t.V8IntrinsicIdentifier>,
functionName: string
functionName: string,
): boolean {
const node = path.node;
return (
(node.type === "Identifier" && node.name === functionName) ||
(node.type === "MemberExpression" &&
node.object.type === "Identifier" &&
node.object.name === "React" &&
node.property.type === "Identifier" &&
(node.type === 'Identifier' && node.name === functionName) ||
(node.type === 'MemberExpression' &&
node.object.type === 'Identifier' &&
node.object.name === 'React' &&
node.property.type === 'Identifier' &&
node.property.name === functionName)
);
}
@@ -693,8 +748,8 @@ function isReactAPI(
function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
return !!(
path.parentPath.isCallExpression() &&
path.parentPath.get("callee").isExpression() &&
isReactAPI(path.parentPath.get("callee"), "forwardRef")
path.parentPath.get('callee').isExpression() &&
isReactAPI(path.parentPath.get('callee'), 'forwardRef')
);
}
@@ -706,50 +761,50 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
function isMemoCallback(path: NodePath<t.Expression>): boolean {
return (
path.parentPath.isCallExpression() &&
path.parentPath.get("callee").isExpression() &&
isReactAPI(path.parentPath.get("callee"), "memo")
path.parentPath.get('callee').isExpression() &&
isReactAPI(path.parentPath.get('callee'), 'memo')
);
}
function isValidPropsAnnotation(
annot: t.TypeAnnotation | t.TSTypeAnnotation | t.Noop | null | undefined
annot: t.TypeAnnotation | t.TSTypeAnnotation | t.Noop | null | undefined,
): boolean {
if (annot == null) {
return true;
} else if (annot.type === "TSTypeAnnotation") {
} else if (annot.type === 'TSTypeAnnotation') {
switch (annot.typeAnnotation.type) {
case "TSArrayType":
case "TSBigIntKeyword":
case "TSBooleanKeyword":
case "TSConstructorType":
case "TSFunctionType":
case "TSLiteralType":
case "TSNeverKeyword":
case "TSNumberKeyword":
case "TSStringKeyword":
case "TSSymbolKeyword":
case "TSTupleType":
case 'TSArrayType':
case 'TSBigIntKeyword':
case 'TSBooleanKeyword':
case 'TSConstructorType':
case 'TSFunctionType':
case 'TSLiteralType':
case 'TSNeverKeyword':
case 'TSNumberKeyword':
case 'TSStringKeyword':
case 'TSSymbolKeyword':
case 'TSTupleType':
return false;
}
return true;
} else if (annot.type === "TypeAnnotation") {
} else if (annot.type === 'TypeAnnotation') {
switch (annot.typeAnnotation.type) {
case "ArrayTypeAnnotation":
case "BooleanLiteralTypeAnnotation":
case "BooleanTypeAnnotation":
case "EmptyTypeAnnotation":
case "FunctionTypeAnnotation":
case "NumberLiteralTypeAnnotation":
case "NumberTypeAnnotation":
case "StringLiteralTypeAnnotation":
case "StringTypeAnnotation":
case "SymbolTypeAnnotation":
case "ThisTypeAnnotation":
case "TupleTypeAnnotation":
case 'ArrayTypeAnnotation':
case 'BooleanLiteralTypeAnnotation':
case 'BooleanTypeAnnotation':
case 'EmptyTypeAnnotation':
case 'FunctionTypeAnnotation':
case 'NumberLiteralTypeAnnotation':
case 'NumberTypeAnnotation':
case 'StringLiteralTypeAnnotation':
case 'StringTypeAnnotation':
case 'SymbolTypeAnnotation':
case 'ThisTypeAnnotation':
case 'TupleTypeAnnotation':
return false;
}
return true;
} else if (annot.type === "Noop") {
} else if (annot.type === 'Noop') {
return true;
} else {
assertExhaustive(annot, `Unexpected annotation node \`${annot}\``);
@@ -757,7 +812,7 @@ function isValidPropsAnnotation(
}
function isValidComponentParams(
params: Array<NodePath<t.Identifier | t.Pattern | t.RestElement>>
params: Array<NodePath<t.Identifier | t.Pattern | t.RestElement>>,
): boolean {
if (params.length === 0) {
return true;
@@ -770,8 +825,8 @@ function isValidComponentParams(
return !params[0].isRestElement();
} else if (params[1].isIdentifier()) {
// check if second param might be a ref
const { name } = params[1].node;
return name.includes("ref") || name.includes("Ref");
const {name} = params[1].node;
return name.includes('ref') || name.includes('Ref');
} else {
/**
* Otherwise, avoid helper functions that take more than one argument.
@@ -792,19 +847,19 @@ function getComponentOrHookLike(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
hookPattern: string | null
hookPattern: string | null,
): ReactFunctionType | null {
const functionName = getFunctionName(node);
// Check if the name is component or hook like:
if (functionName !== null && isComponentName(functionName)) {
let isComponent =
callsHooksOrCreatesJsx(node, hookPattern) &&
isValidComponentParams(node.get("params")) &&
isValidComponentParams(node.get('params')) &&
!returnsNonNode(node);
return isComponent ? "Component" : null;
return isComponent ? 'Component' : null;
} else if (functionName !== null && isHook(functionName, hookPattern)) {
// Hooks have hook invocations or JSX, but can take any # of arguments
return callsHooksOrCreatesJsx(node, hookPattern) ? "Hook" : null;
return callsHooksOrCreatesJsx(node, hookPattern) ? 'Hook' : null;
}
/*
@@ -814,7 +869,7 @@ function getComponentOrHookLike(
if (node.isFunctionExpression() || node.isArrowFunctionExpression()) {
if (isForwardRefCallback(node) || isMemoCallback(node)) {
// As an added check we also look for hook invocations or JSX
return callsHooksOrCreatesJsx(node, hookPattern) ? "Component" : null;
return callsHooksOrCreatesJsx(node, hookPattern) ? 'Component' : null;
}
}
return null;
@@ -823,12 +878,12 @@ function getComponentOrHookLike(
function skipNestedFunctions(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
) {
return (
fn: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
): void => {
if (fn.node !== node.node) {
fn.skip();
@@ -840,7 +895,7 @@ function callsHooksOrCreatesJsx(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
hookPattern: string | null
hookPattern: string | null,
): boolean {
let invokesHooks = false;
let createsJsx = false;
@@ -850,7 +905,7 @@ function callsHooksOrCreatesJsx(
createsJsx = true;
},
CallExpression(call) {
const callee = call.get("callee");
const callee = call.get('callee');
if (callee.isExpression() && isHook(callee, hookPattern)) {
invokesHooks = true;
}
@@ -866,7 +921,7 @@ function callsHooksOrCreatesJsx(
function returnsNonNode(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
): boolean {
let hasReturn = false;
let returnsNonNode = false;
@@ -879,12 +934,12 @@ function returnsNonNode(
returnsNonNode = true;
} else {
switch (argument.type) {
case "ObjectExpression":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "BigIntLiteral":
case "ClassExpression":
case "NewExpression": // technically `new Array()` is legit, but unlikely
case 'ObjectExpression':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'BigIntLiteral':
case 'ClassExpression':
case 'NewExpression': // technically `new Array()` is legit, but unlikely
returnsNonNode = true;
}
}
@@ -908,10 +963,10 @@ function returnsNonNode(
function getFunctionName(
path: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
>,
): NodePath<t.Expression> | null {
if (path.isFunctionDeclaration()) {
const id = path.get("id");
const id = path.get('id');
if (id.isIdentifier()) {
return id;
}
@@ -919,31 +974,31 @@ function getFunctionName(
}
let id: NodePath<t.LVal | t.Expression | t.PrivateName> | null = null;
const parent = path.parentPath;
if (parent.isVariableDeclarator() && parent.get("init").node === path.node) {
if (parent.isVariableDeclarator() && parent.get('init').node === path.node) {
// const useHook = () => {};
id = parent.get("id");
id = parent.get('id');
} else if (
parent.isAssignmentExpression() &&
parent.get("right").node === path.node &&
parent.get("operator") === "="
parent.get('right').node === path.node &&
parent.get('operator') === '='
) {
// useHook = () => {};
id = parent.get("left");
id = parent.get('left');
} else if (
parent.isProperty() &&
parent.get("value").node === path.node &&
!parent.get("computed") &&
parent.get("key").isLVal()
parent.get('value').node === path.node &&
!parent.get('computed') &&
parent.get('key').isLVal()
) {
/*
* {useHook: () => {}}
* {useHook() {}}
*/
id = parent.get("key");
id = parent.get('key');
} else if (
parent.isAssignmentPattern() &&
parent.get("right").node === path.node &&
!parent.get("computed")
parent.get('right').node === path.node &&
!parent.get('computed')
) {
/*
* const {useHook = () => {}} = {};
@@ -952,7 +1007,7 @@ function getFunctionName(
* Kinda clowny, but we'd said we'd follow spec convention for
* `IsAnonymousFunctionDefinition()` usage.
*/
id = parent.get("left");
id = parent.get('left');
}
if (id !== null && (id.isIdentifier() || id.isMemberExpression())) {
return id;
@@ -963,17 +1018,17 @@ function getFunctionName(
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
program: NodePath<t.Program>,
fns: Array<BabelFn>
fns: Array<BabelFn>,
): CompilerError | null {
const fnIds = new Set(
fns
.map((fn) => getFunctionName(fn))
.map(fn => getFunctionName(fn))
.filter(
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier()
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
)
.map((name) => name.node)
.map(name => name.node),
);
const fnNames = new Map([...fnIds].map((id) => [id.name, id]));
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
const errors = new CompilerError();
program.traverse({
@@ -1019,7 +1074,7 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
loc: fn.loc ?? null,
suggestions: null,
severity: ErrorSeverity.Invariant,
})
}),
);
}
},

View File

@@ -1,13 +1,13 @@
import type * as BabelCore from "@babel/core";
import { hasOwnProperty } from "../Utils/utils";
import { PluginOptions } from "./Options";
import type * as BabelCore from '@babel/core';
import {hasOwnProperty} from '../Utils/utils';
import {PluginOptions} from './Options';
function hasModule(name: string): boolean {
try {
return !!require.resolve(name);
} catch (error: any) {
if (
error.code === "MODULE_NOT_FOUND" &&
error.code === 'MODULE_NOT_FOUND' &&
error.message.indexOf(name) !== -1
) {
return false;
@@ -24,22 +24,22 @@ function hasModule(name: string): boolean {
* See https://github.com/expo/expo/blob/e4b8d86442482c7316365a6b7ec1141eec73409d/packages/babel-preset-expo/src/index.ts#L300-L301
*/
export function pipelineUsesReanimatedPlugin(
plugins: Array<BabelCore.PluginItem> | null | undefined
plugins: Array<BabelCore.PluginItem> | null | undefined,
): boolean {
if (Array.isArray(plugins)) {
for (const plugin of plugins) {
if (hasOwnProperty(plugin, "key")) {
if (hasOwnProperty(plugin, 'key')) {
const key = (plugin as any).key; // already checked
if (
typeof key === "string" &&
key.indexOf("react-native-reanimated") !== -1
typeof key === 'string' &&
key.indexOf('react-native-reanimated') !== -1
) {
return true;
}
}
}
}
return hasModule("react-native-reanimated");
return hasModule('react-native-reanimated');
}
export function injectReanimatedFlag(options: PluginOptions): PluginOptions {

View File

@@ -5,15 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import { NodePath } from "@babel/core";
import * as t from "@babel/types";
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetail,
CompilerSuggestionOperation,
ErrorSeverity,
} from "../CompilerError";
import { assertExhaustive } from "../Utils/utils";
} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
/**
* Captures the start and end range of a pair of eslint-disable ... eslint-enable comments. In the
@@ -29,7 +29,7 @@ export type SuppressionRange = {
source: SuppressionSource;
};
type SuppressionSource = "Eslint" | "Flow";
type SuppressionSource = 'Eslint' | 'Flow';
/**
* An suppression affects a function if:
@@ -38,7 +38,7 @@ type SuppressionSource = "Eslint" | "Flow";
*/
export function filterSuppressionsThatAffectFunction(
suppressionRanges: Array<SuppressionRange>,
fn: NodePath<t.Function>
fn: NodePath<t.Function>,
): Array<SuppressionRange> {
const suppressionsInScope: Array<SuppressionRange> = [];
const fnNode = fn.node;
@@ -78,21 +78,21 @@ export function filterSuppressionsThatAffectFunction(
export function findProgramSuppressions(
programComments: Array<t.Comment>,
ruleNames: Array<string>,
flowSuppressions: boolean
flowSuppressions: boolean,
): Array<SuppressionRange> {
const suppressionRanges: Array<SuppressionRange> = [];
let disableComment: t.Comment | null = null;
let enableComment: t.Comment | null = null;
let source: SuppressionSource | null = null;
const rulePattern = `(${ruleNames.join("|")})`;
const rulePattern = `(${ruleNames.join('|')})`;
const disableNextLinePattern = new RegExp(
`eslint-disable-next-line ${rulePattern}`
`eslint-disable-next-line ${rulePattern}`,
);
const disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
const enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
const flowSuppressionPattern = new RegExp(
"\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule"
'\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule',
);
for (const comment of programComments) {
@@ -110,7 +110,7 @@ export function findProgramSuppressions(
) {
disableComment = comment;
enableComment = comment;
source = "Eslint";
source = 'Eslint';
}
if (
@@ -120,15 +120,15 @@ export function findProgramSuppressions(
) {
disableComment = comment;
enableComment = comment;
source = "Flow";
source = 'Flow';
}
if (disablePattern.test(comment.value)) {
disableComment = comment;
source = "Eslint";
source = 'Eslint';
}
if (enablePattern.test(comment.value) && source === "Eslint") {
if (enablePattern.test(comment.value) && source === 'Eslint') {
enableComment = comment;
}
@@ -147,7 +147,7 @@ export function findProgramSuppressions(
}
export function suppressionsToCompilerError(
suppressionRanges: Array<SuppressionRange>
suppressionRanges: Array<SuppressionRange>,
): CompilerError | null {
if (suppressionRanges.length === 0) {
return null;
@@ -162,21 +162,21 @@ export function suppressionsToCompilerError(
}
let reason, suggestion;
switch (suppressionRange.source) {
case "Eslint":
case 'Eslint':
reason =
"React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled";
'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled';
suggestion =
"Remove the ESLint suppression and address the React error";
'Remove the ESLint suppression and address the React error';
break;
case "Flow":
case 'Flow':
reason =
"React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow";
suggestion = "Remove the Flow suppression and address the React error";
'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow';
suggestion = 'Remove the Flow suppression and address the React error';
break;
default:
assertExhaustive(
suppressionRange.source,
"Unhandled suppression source"
'Unhandled suppression source',
);
}
error.pushErrorDetail(
@@ -195,7 +195,7 @@ export function suppressionsToCompilerError(
op: CompilerSuggestionOperation.Remove,
},
],
})
}),
);
}
return error;

View File

@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
export * from "./Gating";
export * from "./Imports";
export * from "./Options";
export * from "./Pipeline";
export * from "./Program";
export * from "./Suppression";
export * from './Gating';
export * from './Imports';
export * from './Options';
export * from './Pipeline';
export * from './Program';
export * from './Suppression';

View File

@@ -5,20 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/
import { CompilerError } from "../CompilerError";
import {CompilerError} from '../CompilerError';
import {
GeneratedSource,
HIRFunction,
Identifier,
IdentifierId,
SourceLocation,
} from "./HIR";
import { printPlace } from "./PrintHIR";
} from './HIR';
import {printPlace} from './PrintHIR';
import {
eachInstructionLValue,
eachInstructionValueOperand,
eachTerminalOperand,
} from "./visitors";
} from './visitors';
/*
* Validation pass to check that there is a 1:1 mapping between Identifier objects and IdentifierIds,
@@ -44,7 +44,7 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
reason: `Expected lvalues to be assigned exactly once`,
description: `Found duplicate assignment of '${printPlace(
instr.lvalue
instr.lvalue,
)}'`,
loc: instr.lvalue.loc,
suggestions: null,
@@ -68,7 +68,7 @@ type Identifiers = Map<IdentifierId, Identifier>;
function validate(
identifiers: Identifiers,
identifier: Identifier,
loc: SourceLocation | null = null
loc: SourceLocation | null = null,
): void {
const previous = identifiers.get(identifier.id);
if (previous === undefined) {

View File

@@ -5,18 +5,18 @@
* LICENSE file in the root directory of this source tree.
*/
import { CompilerError } from "../CompilerError";
import { GeneratedSource, HIRFunction } from "./HIR";
import { printTerminal } from "./PrintHIR";
import { eachTerminalSuccessor, mapTerminalSuccessors } from "./visitors";
import {CompilerError} from '../CompilerError';
import {GeneratedSource, HIRFunction} from './HIR';
import {printTerminal} from './PrintHIR';
import {eachTerminalSuccessor, mapTerminalSuccessors} from './visitors';
export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
mapTerminalSuccessors(block.terminal, (successor) => {
mapTerminalSuccessors(block.terminal, successor => {
CompilerError.invariant(fn.body.blocks.has(successor), {
reason: `Terminal successor references unknown block`,
description: `Block bb${successor} does not exist for terminal '${printTerminal(
block.terminal
block.terminal,
)}'`,
loc: (block.terminal as any).loc ?? GeneratedSource,
suggestions: null,
@@ -31,16 +31,17 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
for (const pred of block.preds) {
const predBlock = fn.body.blocks.get(pred);
CompilerError.invariant(predBlock != null, {
reason: "Expected predecessor block to exist",
reason: 'Expected predecessor block to exist',
description: `Block ${block.id} references non-existent ${pred}`,
loc: GeneratedSource,
});
CompilerError.invariant(
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
{
reason: "Terminal successor does not reference correct predecessor",
reason: 'Terminal successor does not reference correct predecessor',
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
loc: GeneratedSource,
}
},
);
}
}

View File

@@ -1,4 +1,4 @@
import { CompilerError } from "..";
import {CompilerError} from '..';
import {
BlockId,
GeneratedSource,
@@ -7,13 +7,13 @@ import {
Place,
ReactiveScope,
ScopeId,
} from "./HIR";
} from './HIR';
import {
eachInstructionLValue,
eachInstructionOperand,
eachTerminalOperand,
terminalFallthrough,
} from "./visitors";
} from './visitors';
/**
* This pass asserts that program blocks and scopes properly form a tree hierarchy
@@ -41,11 +41,11 @@ import {
*/
type Block =
| ({
kind: "ProgramBlockSubtree";
kind: 'ProgramBlockSubtree';
id: BlockId;
} & MutableRange)
| ({
kind: "Scope";
kind: 'Scope';
id: ScopeId;
} & MutableRange);
@@ -96,7 +96,7 @@ export function getScopes(fn: HIRFunction): Set<ReactiveScope> {
*/
export function rangePreOrderComparator(
a: MutableRange,
b: MutableRange
b: MutableRange,
): number {
const startDiff = a.start - b.start;
if (startDiff !== 0) return startDiff;
@@ -108,7 +108,7 @@ export function recursivelyTraverseItems<T, TContext>(
getRange: (val: T) => MutableRange,
context: TContext,
enter: (val: T, context: TContext) => void,
exit: (val: T, context: TContext) => void
exit: (val: T, context: TContext) => void,
): void {
items.sort((a, b) => rangePreOrderComparator(getRange(a), getRange(b)));
let activeItems: Array<T> = [];
@@ -122,7 +122,7 @@ export function recursivelyTraverseItems<T, TContext>(
const disjoint = currRange.start >= maybeParentRange.end;
const nested = currRange.end <= maybeParentRange.end;
CompilerError.invariant(disjoint || nested, {
reason: "Invalid nesting in program blocks or scopes",
reason: 'Invalid nesting in program blocks or scopes',
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
loc: GeneratedSource,
});
@@ -148,8 +148,8 @@ const no_op: () => void = () => {};
export function assertValidBlockNesting(fn: HIRFunction): void {
const scopes = getScopes(fn);
const blocks: Array<Block> = [...scopes].map((scope) => ({
kind: "Scope",
const blocks: Array<Block> = [...scopes].map(scope => ({
kind: 'Scope',
id: scope.id,
...scope.range,
})) as Array<Block>;
@@ -159,7 +159,7 @@ export function assertValidBlockNesting(fn: HIRFunction): void {
const fallthrough = fn.body.blocks.get(fallthroughId)!;
const end = fallthrough.instructions[0]?.id ?? fallthrough.terminal.id;
blocks.push({
kind: "ProgramBlockSubtree",
kind: 'ProgramBlockSubtree',
id: block.id,
start: block.terminal.id,
end,
@@ -167,5 +167,5 @@ export function assertValidBlockNesting(fn: HIRFunction): void {
}
}
recursivelyTraverseItems(blocks, (block) => block, null, no_op, no_op);
recursivelyTraverseItems(blocks, block => block, null, no_op, no_op);
}

View File

@@ -5,13 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import invariant from "invariant";
import { HIRFunction, Identifier, MutableRange } from "./HIR";
import invariant from 'invariant';
import {HIRFunction, Identifier, MutableRange} from './HIR';
import {
eachInstructionLValue,
eachInstructionOperand,
eachTerminalOperand,
} from "./visitors";
} from './visitors';
/*
* Checks that all mutable ranges in the function are well-formed, with
@@ -49,8 +49,8 @@ function validateMutableRange(mutableRange: MutableRange): void {
invariant(
(mutableRange.start === 0 && mutableRange.end === 0) ||
mutableRange.end > mutableRange.start,
"Identifier scope mutableRange was invalid: [%s:%s]",
'Identifier scope mutableRange was invalid: [%s:%s]',
mutableRange.start,
mutableRange.end
mutableRange.end,
);
}

View File

@@ -1,6 +1,6 @@
import { CompilerError } from "../CompilerError";
import { getScopes, recursivelyTraverseItems } from "./AssertValidBlockNesting";
import { Environment } from "./Environment";
import {CompilerError} from '../CompilerError';
import {getScopes, recursivelyTraverseItems} from './AssertValidBlockNesting';
import {Environment} from './Environment';
import {
BasicBlock,
BlockId,
@@ -12,7 +12,12 @@ import {
ReactiveScope,
ReactiveScopeTerminal,
ScopeId,
} from "./HIR";
} from './HIR';
import {
markInstructionIds,
markPredecessors,
reversePostorderBlocks,
} from './HIRBuilder';
/**
* This pass assumes that all program blocks are properly nested with respect to fallthroughs
@@ -72,14 +77,14 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
const queuedRewrites: Array<TerminalRewriteInfo> = [];
recursivelyTraverseItems(
[...getScopes(fn)],
(scope) => scope.range,
scope => scope.range,
{
fallthroughs: new Map(),
rewrites: queuedRewrites,
env: fn.env,
},
pushStartScopeTerminal,
pushEndScopeTerminal
pushEndScopeTerminal,
);
/**
@@ -142,16 +147,9 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
/**
* Step 3:
* Repoint preds and phis when they refer to a rewritten block.
* Repoint phis when they refer to a rewritten block.
*/
for (const [, block] of originalBlocks) {
for (const pred of block.preds) {
const newId = rewrittenFinalBlocks.get(pred);
if (newId != null) {
block.preds.delete(pred);
block.preds.add(newId);
}
}
for (const phi of block.phis) {
for (const [originalId, value] of phi.operands) {
const newId = rewrittenFinalBlocks.get(originalId);
@@ -162,18 +160,48 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
}
}
}
/**
* Step 4:
* Fixup the HIR to restore RPO, ensure correct predecessors, and
* renumber instructions. Note that the renumbering instructions
* invalidates scope and identifier ranges, so we fix them in the
* next step.
*/
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
/**
* Step 5:
* Fix scope and identifier ranges to account for renumbered instructions
*/
for (const [, block] of fn.body.blocks) {
const terminal = block.terminal;
if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
/*
* Scope ranges should always align to start at the 'scope' terminal
* and end at the first instruction of the fallthrough block
*/
const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!;
const firstId =
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
terminal.scope.range.start = terminal.id;
terminal.scope.range.end = firstId;
}
}
}
type TerminalRewriteInfo =
| {
kind: "StartScope";
kind: 'StartScope';
blockId: BlockId;
fallthroughId: BlockId;
instrId: InstructionId;
scope: ReactiveScope;
}
| {
kind: "EndScope";
kind: 'EndScope';
instrId: InstructionId;
fallthroughId: BlockId;
};
@@ -190,12 +218,12 @@ type ScopeTraversalContext = {
function pushStartScopeTerminal(
scope: ReactiveScope,
context: ScopeTraversalContext
context: ScopeTraversalContext,
): void {
const blockId = context.env.nextBlockId;
const fallthroughId = context.env.nextBlockId;
context.rewrites.push({
kind: "StartScope",
kind: 'StartScope',
blockId,
fallthroughId,
instrId: scope.range.start,
@@ -206,15 +234,15 @@ function pushStartScopeTerminal(
function pushEndScopeTerminal(
scope: ReactiveScope,
context: ScopeTraversalContext
context: ScopeTraversalContext,
): void {
const fallthroughId = context.fallthroughs.get(scope.id);
CompilerError.invariant(fallthroughId != null, {
reason: "Expected scope to exist",
reason: 'Expected scope to exist',
loc: GeneratedSource,
});
context.rewrites.push({
kind: "EndScope",
kind: 'EndScope',
fallthroughId,
instrId: scope.range.end,
});
@@ -248,13 +276,13 @@ type RewriteContext = {
function handleRewrite(
terminalInfo: TerminalRewriteInfo,
idx: number,
context: RewriteContext
context: RewriteContext,
): void {
// TODO make consistent instruction IDs instead of reusing
const terminal: ReactiveScopeTerminal | GotoTerminal =
terminalInfo.kind === "StartScope"
terminalInfo.kind === 'StartScope'
? {
kind: "scope",
kind: 'scope',
fallthrough: terminalInfo.fallthroughId,
block: terminalInfo.blockId,
scope: terminalInfo.scope,
@@ -262,7 +290,7 @@ function handleRewrite(
loc: GeneratedSource,
}
: {
kind: "goto",
kind: 'goto',
variant: GotoVariant.Break,
block: terminalInfo.fallthroughId,
id: terminalInfo.instrId,
@@ -281,7 +309,7 @@ function handleRewrite(
});
context.nextPreds = new Set([currBlockId]);
context.nextBlockId =
terminalInfo.kind === "StartScope"
terminalInfo.kind === 'StartScope'
? terminalInfo.blockId
: terminalInfo.fallthroughId;
context.instrSliceIdx = idx;

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import { BlockId, HIRFunction, computePostDominatorTree } from ".";
import { CompilerError } from "..";
import {BlockId, HIRFunction, computePostDominatorTree} from '.';
import {CompilerError} from '..';
export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
// Construct the set of blocks that is always reachable from the entry block.
@@ -23,7 +23,7 @@ export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
while (current !== null && current !== exit) {
CompilerError.invariant(!unconditionalBlocks.has(current), {
reason:
"Internal error: non-terminating loop in ComputeUnconditionalBlocks",
'Internal error: non-terminating loop in ComputeUnconditionalBlocks',
loc: null,
suggestions: null,
});

View File

@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import prettyFormat from "pretty-format";
import { CompilerError } from "../CompilerError";
import { BlockId, HIRFunction } from "./HIR";
import { eachTerminalSuccessor } from "./visitors";
import prettyFormat from 'pretty-format';
import {CompilerError} from '../CompilerError';
import {BlockId, HIRFunction} from './HIR';
import {eachTerminalSuccessor} from './visitors';
/*
* Computes the dominator tree of the given function. The returned `Dominator` stores the immediate
@@ -34,7 +34,7 @@ export function computeDominatorTree(fn: HIRFunction): Dominator<BlockId> {
*/
export function computePostDominatorTree(
fn: HIRFunction,
options: { includeThrowsAsExitNode: boolean }
options: {includeThrowsAsExitNode: boolean},
): PostDominator<BlockId> {
const graph = buildReverseGraph(fn, options.includeThrowsAsExitNode);
const nodes = computeImmediateDominators(graph);
@@ -87,7 +87,7 @@ export class Dominator<T> {
get(id: T): T | null {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, {
reason: "Unknown node",
reason: 'Unknown node',
description: null,
loc: null,
suggestions: null,
@@ -128,7 +128,7 @@ export class PostDominator<T> {
get(id: T): T | null {
const dominator = this.#nodes.get(id);
CompilerError.invariant(dominator !== undefined, {
reason: "Unknown node",
reason: 'Unknown node',
description: null,
loc: null,
suggestions: null,
@@ -217,7 +217,7 @@ function intersect<T>(a: T, b: T, graph: Graph<T>, nodes: Map<T, T>): T {
// Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation
function buildGraph(fn: HIRFunction): Graph<BlockId> {
const graph: Graph<BlockId> = { entry: fn.body.entry, nodes: new Map() };
const graph: Graph<BlockId> = {entry: fn.body.entry, nodes: new Map()};
let index = 0;
for (const [id, block] of fn.body.blocks) {
graph.nodes.set(id, {
@@ -237,7 +237,7 @@ function buildGraph(fn: HIRFunction): Graph<BlockId> {
*/
function buildReverseGraph(
fn: HIRFunction,
includeThrowsAsExitNode: boolean
includeThrowsAsExitNode: boolean,
): Graph<BlockId> {
const nodes: Map<BlockId, Node<BlockId>> = new Map();
const exitId = fn.env.nextBlockId;
@@ -256,10 +256,10 @@ function buildReverseGraph(
preds: new Set(eachTerminalSuccessor(block.terminal)),
succs: new Set(block.preds),
};
if (block.terminal.kind === "return") {
if (block.terminal.kind === 'return') {
node.preds.add(exitId);
exit.succs.add(id);
} else if (block.terminal.kind === "throw" && includeThrowsAsExitNode) {
} else if (block.terminal.kind === 'throw' && includeThrowsAsExitNode) {
node.preds.add(exitId);
exit.succs.add(id);
}
@@ -282,7 +282,7 @@ function buildReverseGraph(
}
visit(exitId);
const rpo: Graph<BlockId> = { entry: exitId, nodes: new Map() };
const rpo: Graph<BlockId> = {entry: exitId, nodes: new Map()};
let index = 0;
for (const id of postorder.reverse()) {
const node = nodes.get(id)!;

View File

@@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
import * as t from "@babel/types";
import { ZodError, z } from "zod";
import { fromZodError } from "zod-validation-error";
import { CompilerError } from "../CompilerError";
import { Logger } from "../Entrypoint";
import { Err, Ok, Result } from "../Utils/Result";
import * as t from '@babel/types';
import {ZodError, z} from 'zod';
import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {Logger} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
DEFAULT_GLOBALS,
DEFAULT_SHAPES,
Global,
GlobalRegistry,
installReAnimatedTypes,
} from "./Globals";
} from './Globals';
import {
BlockId,
BuiltInType,
@@ -35,7 +35,7 @@ import {
makeIdentifierId,
makeIdentifierName,
makeScopeId,
} from "./HIR";
} from './HIR';
import {
BuiltInMixedReadonlyId,
DefaultMutatingHook,
@@ -43,8 +43,8 @@ import {
FunctionSignature,
ShapeRegistry,
addHook,
} from "./ObjectShape";
import { Scope as BabelScope } from "@babel/traverse";
} from './ObjectShape';
import {Scope as BabelScope} from '@babel/traverse';
export const ExternalFunctionSchema = z.object({
// Source for the imported module that exports the `importSpecifierName` functions
@@ -61,8 +61,8 @@ export const InstrumentationSchema = z
globalGating: z.string().nullish(),
})
.refine(
(opts) => opts.gating != null || opts.globalGating != null,
"Expected at least one of gating or globalGating"
opts => opts.gating != null || opts.globalGating != null,
'Expected at least one of gating or globalGating',
);
export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
@@ -443,34 +443,34 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
// Get the defaults to programmatically check for boolean properties
const defaultConfig = EnvironmentConfigSchema.parse({});
for (const token of pragma.split(" ")) {
if (!token.startsWith("@")) {
for (const token of pragma.split(' ')) {
if (!token.startsWith('@')) {
continue;
}
const keyVal = token.slice(1);
let [key, val]: any = keyVal.split(":");
let [key, val]: any = keyVal.split(':');
if (key === "validateNoCapitalizedCalls") {
if (key === 'validateNoCapitalizedCalls') {
maybeConfig[key] = [];
continue;
}
if (
key === "enableChangeDetectionForDebugging" &&
(val === undefined || val === "true")
key === 'enableChangeDetectionForDebugging' &&
(val === undefined || val === 'true')
) {
maybeConfig[key] = {
source: "react-compiler-runtime",
importSpecifierName: "$structuralCheck",
source: 'react-compiler-runtime',
importSpecifierName: '$structuralCheck',
};
continue;
}
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== "boolean") {
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') {
// skip parsing non-boolean properties
continue;
}
if (val === undefined || val === "true") {
if (val === undefined || val === 'true') {
val = true;
} else {
val = false;
@@ -483,7 +483,7 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
return config.data;
}
CompilerError.invariant(false, {
reason: "Internal error, could not parse config from pragma string",
reason: 'Internal error, could not parse config from pragma string',
description: `${fromZodError(config.error)}`,
loc: null,
suggestions: null,
@@ -492,18 +492,18 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
export type ReactFunctionType = "Component" | "Hook" | "Other";
export type ReactFunctionType = 'Component' | 'Hook' | 'Other';
export function printFunctionType(type: ReactFunctionType): string {
switch (type) {
case "Component": {
return "component";
case 'Component': {
return 'component';
}
case "Hook": {
return "hook";
case 'Hook': {
return 'hook';
}
default: {
return "function";
return 'function';
}
}
}
@@ -537,7 +537,7 @@ export class Environment {
logger: Logger | null,
filename: string | null,
code: string | null,
useMemoCacheIdentifier: string
useMemoCacheIdentifier: string,
) {
this.#scope = scope;
this.fnType = fnType;
@@ -574,13 +574,13 @@ export class Environment {
positionalParams: [],
restParam: hook.effectKind,
returnType: hook.transitiveMixedData
? { kind: "Object", shapeId: BuiltInMixedReadonlyId }
: { kind: "Poly" },
? {kind: 'Object', shapeId: BuiltInMixedReadonlyId}
: {kind: 'Poly'},
returnValueKind: hook.valueKind,
calleeEffect: Effect.Read,
hookKind: "Custom",
hookKind: 'Custom',
noAlias: hook.noAlias,
})
}),
);
}
@@ -613,14 +613,14 @@ export class Environment {
}
generateGloballyUniqueIdentifierName(
name: string | null
name: string | null,
): ValidatedIdentifier {
const identifierNode = this.#scope.generateUidIdentifier(name ?? undefined);
return makeIdentifierName(identifierNode.name);
}
outlineFunction(fn: HIRFunction, type: ReactFunctionType | null): void {
this.#outlinedFunctions.push({ fn, type });
this.#outlinedFunctions.push({fn, type});
}
getOutlinedFunctions(): Array<{
@@ -635,7 +635,7 @@ export class Environment {
const match = new RegExp(this.config.hookPattern).exec(binding.name);
if (
match != null &&
typeof match[1] === "string" &&
typeof match[1] === 'string' &&
isHookName(match[1])
) {
const resolvedName = match[1];
@@ -644,17 +644,17 @@ export class Environment {
}
switch (binding.kind) {
case "ModuleLocal": {
case 'ModuleLocal': {
// don't resolve module locals
return isHookName(binding.name) ? this.#getCustomHookType() : null;
}
case "Global": {
case 'Global': {
return (
this.#globals.get(binding.name) ??
(isHookName(binding.name) ? this.#getCustomHookType() : null)
);
}
case "ImportSpecifier": {
case 'ImportSpecifier': {
if (this.#isKnownReactModule(binding.module)) {
/**
* For `import {imported as name} from "..."` form, we use the `imported`
@@ -681,8 +681,8 @@ export class Environment {
: null;
}
}
case "ImportDefault":
case "ImportNamespace": {
case 'ImportDefault':
case 'ImportNamespace': {
if (this.#isKnownReactModule(binding.module)) {
// only resolve imports to modules we know about
return (
@@ -698,19 +698,19 @@ export class Environment {
#isKnownReactModule(moduleName: string): boolean {
return (
moduleName.toLowerCase() === "react" ||
moduleName.toLowerCase() === "react-dom" ||
moduleName.toLowerCase() === 'react' ||
moduleName.toLowerCase() === 'react-dom' ||
(this.config.enableSharedRuntime__testonly &&
moduleName === "shared-runtime")
moduleName === 'shared-runtime')
);
}
getPropertyType(
receiver: Type,
property: string
property: string,
): BuiltInType | PolyType | null {
let shapeId = null;
if (receiver.kind === "Object" || receiver.kind === "Function") {
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
shapeId = receiver.shapeId;
}
if (shapeId !== null) {
@@ -726,7 +726,7 @@ export class Environment {
suggestions: null,
});
let value =
shape.properties.get(property) ?? shape.properties.get("*") ?? null;
shape.properties.get(property) ?? shape.properties.get('*') ?? null;
if (value === null && isHookName(property)) {
value = this.#getCustomHookType();
}
@@ -739,7 +739,7 @@ export class Environment {
}
getFunctionSignature(type: FunctionType): FunctionSignature | null {
const { shapeId } = type;
const {shapeId} = type;
if (shapeId !== null) {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(shape !== undefined, {
@@ -773,7 +773,7 @@ export function isHookName(name: string): boolean {
}
export function parseEnvironmentConfig(
partialConfig: PartialEnvironmentConfig
partialConfig: PartialEnvironmentConfig,
): Result<EnvironmentConfig, ZodError<PartialEnvironmentConfig>> {
const config = EnvironmentConfigSchema.safeParse(partialConfig);
if (config.success) {
@@ -784,7 +784,7 @@ export function parseEnvironmentConfig(
}
export function validateEnvironmentConfig(
partialConfig: PartialEnvironmentConfig
partialConfig: PartialEnvironmentConfig,
): EnvironmentConfig {
const config = EnvironmentConfigSchema.safeParse(partialConfig);
if (config.success) {
@@ -793,7 +793,7 @@ export function validateEnvironmentConfig(
CompilerError.throwInvalidConfig({
reason:
"Could not validate environment config. Update React Compiler config to fix the error",
'Could not validate environment config. Update React Compiler config to fix the error',
description: `${fromZodError(config.error)}`,
loc: null,
suggestions: null,
@@ -801,10 +801,10 @@ export function validateEnvironmentConfig(
}
export function tryParseExternalFunction(
maybeExternalFunction: any
maybeExternalFunction: any,
): ExternalFunction {
const externalFunction = ExternalFunctionSchema.safeParse(
maybeExternalFunction
maybeExternalFunction,
);
if (externalFunction.success) {
return externalFunction.data;
@@ -812,7 +812,7 @@ export function tryParseExternalFunction(
CompilerError.throwInvalidConfig({
reason:
"Could not parse external function. Update React Compiler config to fix the error",
'Could not parse external function. Update React Compiler config to fix the error',
description: `${fromZodError(externalFunction.error)}`,
loc: null,
suggestions: null,

View File

@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import type { NodePath } from "@babel/traverse";
import type * as t from "@babel/types";
import { CompilerError } from "../CompilerError";
import { getOrInsertDefault } from "../Utils/utils";
import { GeneratedSource } from "./HIR";
import type {NodePath} from '@babel/traverse';
import type * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {getOrInsertDefault} from '../Utils/utils';
import {GeneratedSource} from './HIR';
type IdentifierInfo = {
reassigned: boolean;
@@ -35,7 +35,7 @@ type FindContextIdentifierState = {
const withFunctionScope = {
enter: function (
path: BabelFunction,
state: FindContextIdentifierState
state: FindContextIdentifierState,
): void {
state.currentFn.push(path);
},
@@ -45,7 +45,7 @@ const withFunctionScope = {
};
export function findContextIdentifiers(
func: NodePath<t.Function>
func: NodePath<t.Function>,
): Set<t.Identifier> {
const state: FindContextIdentifierState = {
currentFn: [],
@@ -60,17 +60,17 @@ export function findContextIdentifiers(
ObjectMethod: withFunctionScope,
AssignmentExpression(
path: NodePath<t.AssignmentExpression>,
state: FindContextIdentifierState
state: FindContextIdentifierState,
): void {
const left = path.get("left");
const left = path.get('left');
const currentFn = state.currentFn.at(-1) ?? null;
handleAssignment(currentFn, state.identifiers, left);
},
UpdateExpression(
path: NodePath<t.UpdateExpression>,
state: FindContextIdentifierState
state: FindContextIdentifierState,
): void {
const argument = path.get("argument");
const argument = path.get('argument');
const currentFn = state.currentFn.at(-1) ?? null;
if (argument.isLVal()) {
handleAssignment(currentFn, state.identifiers, argument);
@@ -78,7 +78,7 @@ export function findContextIdentifiers(
},
Identifier(
path: NodePath<t.Identifier>,
state: FindContextIdentifierState
state: FindContextIdentifierState,
): void {
const currentFn = state.currentFn.at(-1) ?? null;
if (path.isReferencedIdentifier()) {
@@ -86,7 +86,7 @@ export function findContextIdentifiers(
}
},
},
state
state,
);
const result = new Set<t.Identifier>();
@@ -103,7 +103,7 @@ export function findContextIdentifiers(
function handleIdentifier(
currentFn: BabelFunction | null,
identifiers: Map<t.Identifier, IdentifierInfo>,
path: NodePath<t.Identifier>
path: NodePath<t.Identifier>,
): void {
const name = path.node.name;
const binding = path.scope.getBinding(name);
@@ -126,7 +126,7 @@ function handleIdentifier(
function handleAssignment(
currentFn: BabelFunction | null,
identifiers: Map<t.Identifier, IdentifierInfo>,
lvalPath: NodePath<t.LVal>
lvalPath: NodePath<t.LVal>,
): void {
/*
* Find all reassignments to identifiers declared outside of currentFn
@@ -134,7 +134,7 @@ function handleAssignment(
*/
const lvalNode = lvalPath.node;
switch (lvalNode.type) {
case "Identifier": {
case 'Identifier': {
const path = lvalPath as NodePath<t.Identifier>;
const name = path.node.name;
const binding = path.scope.getBinding(name);
@@ -155,20 +155,20 @@ function handleAssignment(
}
break;
}
case "ArrayPattern": {
case 'ArrayPattern': {
const path = lvalPath as NodePath<t.ArrayPattern>;
for (const element of path.get("elements")) {
for (const element of path.get('elements')) {
if (nonNull(element)) {
handleAssignment(currentFn, identifiers, element);
}
}
break;
}
case "ObjectPattern": {
case 'ObjectPattern': {
const path = lvalPath as NodePath<t.ObjectPattern>;
for (const property of path.get("properties")) {
for (const property of path.get('properties')) {
if (property.isObjectProperty()) {
const valuePath = property.get("value");
const valuePath = property.get('value');
CompilerError.invariant(valuePath.isLVal(), {
reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
description: null,
@@ -188,18 +188,18 @@ function handleAssignment(
}
break;
}
case "AssignmentPattern": {
case 'AssignmentPattern': {
const path = lvalPath as NodePath<t.AssignmentPattern>;
const left = path.get("left");
const left = path.get('left');
handleAssignment(currentFn, identifiers, left);
break;
}
case "RestElement": {
case 'RestElement': {
const path = lvalPath as NodePath<t.RestElement>;
handleAssignment(currentFn, identifiers, path.get("argument"));
handleAssignment(currentFn, identifiers, path.get('argument'));
break;
}
case "MemberExpression": {
case 'MemberExpression': {
// Interior mutability (not a reassign)
break;
}
@@ -215,7 +215,7 @@ function handleAssignment(
}
function nonNull<T extends NonNullable<t.Node>>(
t: NodePath<T | null>
t: NodePath<T | null>,
): t is NodePath<T> {
return t.node != null;
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { Effect, ValueKind, ValueReason } from "./HIR";
import {Effect, ValueKind, ValueReason} from './HIR';
import {
BUILTIN_SHAPES,
BuiltInArrayId,
@@ -21,8 +21,8 @@ import {
addFunction,
addHook,
addObject,
} from "./ObjectShape";
import { BuiltInType, PolyType } from "./Types";
} from './ObjectShape';
import {BuiltInType, PolyType} from './Types';
/*
* This file exports types and defaults for JavaScript global objects.
@@ -37,59 +37,59 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
// Hack until we add ObjectShapes for all globals
const UNTYPED_GLOBALS: Set<string> = new Set([
"String",
"Object",
"Function",
"Number",
"RegExp",
"Date",
"Error",
"Function",
"TypeError",
"RangeError",
"ReferenceError",
"SyntaxError",
"URIError",
"EvalError",
"Boolean",
"DataView",
"Float32Array",
"Float64Array",
"Int8Array",
"Int16Array",
"Int32Array",
"Map",
"Set",
"WeakMap",
"Uint8Array",
"Uint8ClampedArray",
"Uint16Array",
"Uint32Array",
"ArrayBuffer",
"JSON",
"parseFloat",
"parseInt",
"console",
"isNaN",
"eval",
"isFinite",
"encodeURI",
"decodeURI",
"encodeURIComponent",
"decodeURIComponent",
'String',
'Object',
'Function',
'Number',
'RegExp',
'Date',
'Error',
'Function',
'TypeError',
'RangeError',
'ReferenceError',
'SyntaxError',
'URIError',
'EvalError',
'Boolean',
'DataView',
'Float32Array',
'Float64Array',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',
'Set',
'WeakMap',
'Uint8Array',
'Uint8ClampedArray',
'Uint16Array',
'Uint32Array',
'ArrayBuffer',
'JSON',
'parseFloat',
'parseInt',
'console',
'isNaN',
'eval',
'isFinite',
'encodeURI',
'decodeURI',
'encodeURIComponent',
'decodeURIComponent',
]);
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
[
"Array",
addObject(DEFAULT_SHAPES, "Array", [
'Array',
addObject(DEFAULT_SHAPES, 'Array', [
[
"isArray",
'isArray',
// Array.isArray(value)
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
@@ -106,12 +106,12 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
* function)
*/
[
"of",
'of',
// Array.of(element0, ..., elementN)
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
@@ -119,85 +119,85 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
]),
],
[
"Math",
addObject(DEFAULT_SHAPES, "Math", [
'Math',
addObject(DEFAULT_SHAPES, 'Math', [
// Static properties (TODO)
["PI", { kind: "Primitive" }],
['PI', {kind: 'Primitive'}],
// Static methods (TODO)
[
"max",
'max',
// Math.max(value0, ..., valueN)
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
]),
],
["Infinity", { kind: "Primitive" }],
["NaN", { kind: "Primitive" }],
['Infinity', {kind: 'Primitive'}],
['NaN', {kind: 'Primitive'}],
[
"console",
addObject(DEFAULT_SHAPES, "console", [
'console',
addObject(DEFAULT_SHAPES, 'console', [
[
"error",
'error',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"info",
'info',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"log",
'log',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"table",
'table',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"trace",
'trace',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"warn",
'warn',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
@@ -205,31 +205,31 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
]),
],
[
"Boolean",
'Boolean',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"Number",
'Number',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"String",
'String',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
@@ -244,179 +244,179 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
*/
const REACT_APIS: Array<[string, BuiltInType]> = [
[
"useContext",
'useContext',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "useContext",
hookKind: 'useContext',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.Context,
}),
],
[
"useState",
'useState',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Object", shapeId: BuiltInUseStateId },
returnType: {kind: 'Object', shapeId: BuiltInUseStateId},
calleeEffect: Effect.Read,
hookKind: "useState",
hookKind: 'useState',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.State,
}),
],
[
"useActionState",
'useActionState',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Object", shapeId: BuiltInUseActionStateId },
returnType: {kind: 'Object', shapeId: BuiltInUseActionStateId},
calleeEffect: Effect.Read,
hookKind: "useActionState",
hookKind: 'useActionState',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.State,
}),
],
[
"useReducer",
'useReducer',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Object", shapeId: BuiltInUseReducerId },
returnType: {kind: 'Object', shapeId: BuiltInUseReducerId},
calleeEffect: Effect.Read,
hookKind: "useReducer",
hookKind: 'useReducer',
returnValueKind: ValueKind.Frozen,
returnValueReason: ValueReason.ReducerState,
}),
],
[
"useRef",
'useRef',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Capture,
returnType: { kind: "Object", shapeId: BuiltInUseRefId },
returnType: {kind: 'Object', shapeId: BuiltInUseRefId},
calleeEffect: Effect.Read,
hookKind: "useRef",
hookKind: 'useRef',
returnValueKind: ValueKind.Mutable,
}),
],
[
"useMemo",
'useMemo',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "useMemo",
hookKind: 'useMemo',
returnValueKind: ValueKind.Frozen,
}),
],
[
"useCallback",
'useCallback',
addHook(DEFAULT_SHAPES, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "useCallback",
hookKind: 'useCallback',
returnValueKind: ValueKind.Frozen,
}),
],
[
"useEffect",
'useEffect',
addHook(
DEFAULT_SHAPES,
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
hookKind: "useEffect",
hookKind: 'useEffect',
returnValueKind: ValueKind.Frozen,
},
BuiltInUseEffectHookId
BuiltInUseEffectHookId,
),
],
[
"useLayoutEffect",
'useLayoutEffect',
addHook(
DEFAULT_SHAPES,
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "useLayoutEffect",
hookKind: 'useLayoutEffect',
returnValueKind: ValueKind.Frozen,
},
BuiltInUseLayoutEffectHookId
BuiltInUseLayoutEffectHookId,
),
],
[
"useInsertionEffect",
'useInsertionEffect',
addHook(
DEFAULT_SHAPES,
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "useInsertionEffect",
hookKind: 'useInsertionEffect',
returnValueKind: ValueKind.Frozen,
},
BuiltInUseInsertionEffectHookId
BuiltInUseInsertionEffectHookId,
),
],
[
"use",
'use',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
BuiltInUseOperatorId
BuiltInUseOperatorId,
),
],
];
TYPED_GLOBALS.push(
[
"React",
'React',
addObject(DEFAULT_SHAPES, null, [
...REACT_APIS,
[
"createElement",
'createElement',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
}),
],
[
"cloneElement",
'cloneElement',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
}),
],
[
"createRef",
'createRef',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture, // createRef takes no paramters
returnType: { kind: "Object", shapeId: BuiltInUseRefId },
returnType: {kind: 'Object', shapeId: BuiltInUseRefId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
@@ -424,15 +424,15 @@ TYPED_GLOBALS.push(
]),
],
[
"_jsx",
'_jsx',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
}),
]
],
);
export type Global = BuiltInType | PolyType;
@@ -442,7 +442,7 @@ export const DEFAULT_GLOBALS: GlobalRegistry = new Map(REACT_APIS);
// Hack until we add ObjectShapes for all globals
for (const name of UNTYPED_GLOBALS) {
DEFAULT_GLOBALS.set(name, {
kind: "Poly",
kind: 'Poly',
});
}
@@ -452,22 +452,22 @@ for (const [name, type_] of TYPED_GLOBALS) {
// Recursive global type
DEFAULT_GLOBALS.set(
"globalThis",
addObject(DEFAULT_SHAPES, "globalThis", TYPED_GLOBALS)
'globalThis',
addObject(DEFAULT_SHAPES, 'globalThis', TYPED_GLOBALS),
);
export function installReAnimatedTypes(
globals: GlobalRegistry,
registry: ShapeRegistry
registry: ShapeRegistry,
): void {
// hooks that freeze args and return frozen value
const frozenHooks = [
"useFrameCallback",
"useAnimatedStyle",
"useAnimatedProps",
"useAnimatedScrollHandler",
"useAnimatedReaction",
"useWorkletCallback",
'useFrameCallback',
'useAnimatedStyle',
'useAnimatedProps',
'useAnimatedScrollHandler',
'useAnimatedReaction',
'useWorkletCallback',
];
for (const hook of frozenHooks) {
globals.set(
@@ -475,12 +475,12 @@ export function installReAnimatedTypes(
addHook(registry, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
returnValueKind: ValueKind.Frozen,
noAlias: true,
calleeEffect: Effect.Read,
hookKind: "Custom",
})
hookKind: 'Custom',
}),
);
}
@@ -488,31 +488,31 @@ export function installReAnimatedTypes(
* hooks that return a mutable value. ideally these should be modelled as a
* ref, but this works for now.
*/
const mutableHooks = ["useSharedValue", "useDerivedValue"];
const mutableHooks = ['useSharedValue', 'useDerivedValue'];
for (const hook of mutableHooks) {
globals.set(
hook,
addHook(registry, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
returnValueKind: ValueKind.Mutable,
noAlias: true,
calleeEffect: Effect.Read,
hookKind: "Custom",
})
hookKind: 'Custom',
}),
);
}
// functions that return mutable value
const funcs = [
"withTiming",
"withSpring",
"createAnimatedPropAdapter",
"withDecay",
"withRepeat",
"runOnUI",
"executeOnUIRuntimeSync",
'withTiming',
'withSpring',
'createAnimatedPropAdapter',
'withDecay',
'withRepeat',
'runOnUI',
'executeOnUIRuntimeSync',
];
for (const fn of funcs) {
globals.set(
@@ -520,11 +520,11 @@ export function installReAnimatedTypes(
addFunction(registry, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
noAlias: true,
})
}),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import { Binding, NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import { CompilerError } from "../CompilerError";
import { Environment } from "./Environment";
import {Binding, NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {Environment} from './Environment';
import {
BasicBlock,
BlockId,
@@ -28,13 +28,13 @@ import {
makeIdentifierName,
makeInstructionId,
makeType,
} from "./HIR";
import { printInstruction } from "./PrintHIR";
} from './HIR';
import {printInstruction} from './PrintHIR';
import {
eachTerminalSuccessor,
mapTerminalSuccessors,
terminalFallthrough,
} from "./visitors";
} from './visitors';
/*
* *******************************************************************************************
@@ -54,31 +54,31 @@ export type WipBlock = {
type Scope = LoopScope | LabelScope | SwitchScope;
type LoopScope = {
kind: "loop";
kind: 'loop';
label: string | null;
continueBlock: BlockId;
breakBlock: BlockId;
};
type SwitchScope = {
kind: "switch";
kind: 'switch';
breakBlock: BlockId;
label: string | null;
};
type LabelScope = {
kind: "label";
kind: 'label';
label: string;
breakBlock: BlockId;
};
function newBlock(id: BlockId, kind: BlockKind): WipBlock {
return { id, kind, instructions: [] };
return {id, kind, instructions: []};
}
export type Bindings = Map<
string,
{ node: t.Identifier; identifier: Identifier }
{node: t.Identifier; identifier: Identifier}
>;
/*
@@ -90,13 +90,13 @@ export type ExceptionsMode =
* Mode used for code not covered by explicit exception handling, any
* errors are assumed to be thrown out of the function
*/
| { kind: "ThrowExceptions" }
| {kind: 'ThrowExceptions'}
/*
* Mode used for code that *is* covered by explicit exception handling
* (ie try/catch), which requires modeling the possibility of control
* flow to the exception handler.
*/
| { kind: "CatchExceptions"; handler: BlockId };
| {kind: 'CatchExceptions'; handler: BlockId};
// Helper class for constructing a CFG
export default class HIRBuilder {
@@ -110,6 +110,11 @@ export default class HIRBuilder {
#exceptionHandlerStack: Array<BlockId> = [];
parentFunction: NodePath<t.Function>;
errors: CompilerError = new CompilerError();
/**
* Traversal context: counts the number of `fbt` tag parents
* of the current babel node.
*/
fbtDepth: number = 0;
get nextIdentifierId(): IdentifierId {
return this.#env.nextIdentifierId;
@@ -131,14 +136,14 @@ export default class HIRBuilder {
env: Environment,
parentFunction: NodePath<t.Function>, // the outermost function being compiled
bindings: Bindings | null = null,
context: Array<t.Identifier> | null = null
context: Array<t.Identifier> | null = null,
) {
this.#env = env;
this.#bindings = bindings ?? new Map();
this.parentFunction = parentFunction;
this.#context = context ?? [];
this.#entry = makeBlockId(env.nextBlockId);
this.#current = newBlock(this.#entry, "block");
this.#current = newBlock(this.#entry, 'block');
}
currentBlockKind(): BlockKind {
@@ -153,13 +158,13 @@ export default class HIRBuilder {
const continuationBlock = this.reserve(this.currentBlockKind());
this.terminateWithContinuation(
{
kind: "maybe-throw",
kind: 'maybe-throw',
continuation: continuationBlock.id,
handler: exceptionHandler,
id: makeInstructionId(0),
loc: instruction.loc,
},
continuationBlock
continuationBlock,
);
}
}
@@ -180,7 +185,7 @@ export default class HIRBuilder {
return {
id,
name: null,
mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) },
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
scope: null,
type: makeType(),
loc,
@@ -188,7 +193,7 @@ export default class HIRBuilder {
}
#resolveBabelBinding(
path: NodePath<t.Identifier | t.JSXIdentifier>
path: NodePath<t.Identifier | t.JSXIdentifier>,
): Binding | null {
const originalName = path.node.name;
const binding = path.scope.getBinding(originalName);
@@ -229,12 +234,12 @@ export default class HIRBuilder {
* ```
*/
resolveIdentifier(
path: NodePath<t.Identifier | t.JSXIdentifier>
path: NodePath<t.Identifier | t.JSXIdentifier>,
): VariableBinding {
const originalName = path.node.name;
const babelBinding = this.#resolveBabelBinding(path);
if (babelBinding == null) {
return { kind: "Global", name: originalName };
return {kind: 'Global', name: originalName};
}
// Check if the binding is from module scope
@@ -246,7 +251,7 @@ export default class HIRBuilder {
const importDeclaration =
path.parentPath as NodePath<t.ImportDeclaration>;
return {
kind: "ImportDefault",
kind: 'ImportDefault',
name: originalName,
module: importDeclaration.node.source.value,
};
@@ -254,11 +259,11 @@ export default class HIRBuilder {
const importDeclaration =
path.parentPath as NodePath<t.ImportDeclaration>;
return {
kind: "ImportSpecifier",
kind: 'ImportSpecifier',
name: originalName,
module: importDeclaration.node.source.value,
imported:
path.node.imported.type === "Identifier"
path.node.imported.type === 'Identifier'
? path.node.imported.name
: path.node.imported.value,
};
@@ -266,13 +271,13 @@ export default class HIRBuilder {
const importDeclaration =
path.parentPath as NodePath<t.ImportDeclaration>;
return {
kind: "ImportNamespace",
kind: 'ImportNamespace',
name: originalName,
module: importDeclaration.node.source.value,
};
} else {
return {
kind: "ModuleLocal",
kind: 'ModuleLocal',
name: originalName,
};
}
@@ -283,7 +288,7 @@ export default class HIRBuilder {
babelBinding.scope.rename(originalName, resolvedBinding.name.value);
}
return {
kind: "Identifier",
kind: 'Identifier',
identifier: resolvedBinding,
bindingKind: babelBinding.kind,
};
@@ -294,7 +299,7 @@ export default class HIRBuilder {
if (binding) {
// Check if the binding is from module scope, if so return null
const outerBinding = this.parentFunction.scope.parent.getBinding(
path.node.name
path.node.name,
);
if (binding === outerBinding) {
return false;
@@ -306,6 +311,12 @@ export default class HIRBuilder {
}
resolveBinding(node: t.Identifier): Identifier {
if (node.name === 'fbt') {
CompilerError.throwTodo({
reason: 'Support local variables named "fbt"',
loc: node.loc ?? null,
});
}
const originalName = node.name;
let name = originalName;
let index = 0;
@@ -324,7 +335,7 @@ export default class HIRBuilder {
type: makeType(),
loc: node.loc ?? GeneratedSource,
};
this.#bindings.set(name, { node, identifier });
this.#bindings.set(name, {node, identifier});
return identifier;
} else if (mapping.node === node) {
return mapping.identifier;
@@ -345,7 +356,7 @@ export default class HIRBuilder {
if (
!rpoBlocks.has(id) &&
block.instructions.some(
(instr) => instr.value.kind === "FunctionExpression"
instr => instr.value.kind === 'FunctionExpression',
)
) {
CompilerError.throwTodo({
@@ -369,7 +380,7 @@ export default class HIRBuilder {
// Terminate the current block w the given terminal, and start a new block
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
const { id: blockId, kind, instructions } = this.#current;
const {id: blockId, kind, instructions} = this.#current;
this.#completed.set(blockId, {
kind,
id: blockId,
@@ -389,7 +400,7 @@ export default class HIRBuilder {
* reserved block as the new current block
*/
terminateWithContinuation(terminal: Terminal, continuation: WipBlock): void {
const { id: blockId, kind, instructions } = this.#current;
const {id: blockId, kind, instructions} = this.#current;
this.#completed.set(blockId, {
kind: kind,
id: blockId,
@@ -412,7 +423,7 @@ export default class HIRBuilder {
// Save a previously reserved block as completed
complete(block: WipBlock, terminal: Terminal): void {
const { id: blockId, kind, instructions } = block;
const {id: blockId, kind, instructions} = block;
this.#completed.set(blockId, {
kind,
id: blockId,
@@ -431,7 +442,7 @@ export default class HIRBuilder {
const current = this.#current;
this.#current = wip;
const terminal = fn();
const { id: blockId, kind, instructions } = this.#current;
const {id: blockId, kind, instructions} = this.#current;
this.#completed.set(blockId, {
kind,
id: blockId,
@@ -459,7 +470,7 @@ export default class HIRBuilder {
label<T>(label: string, breakBlock: BlockId, fn: () => T): T {
this.#scopes.push({
kind: "label",
kind: 'label',
breakBlock,
label,
});
@@ -467,22 +478,22 @@ export default class HIRBuilder {
const last = this.#scopes.pop();
CompilerError.invariant(
last != null &&
last.kind === "label" &&
last.kind === 'label' &&
last.label === label &&
last.breakBlock === breakBlock,
{
reason: "Mismatched label",
reason: 'Mismatched label',
description: null,
loc: null,
suggestions: null,
}
},
);
return value;
}
switch<T>(label: string | null, breakBlock: BlockId, fn: () => T): T {
this.#scopes.push({
kind: "switch",
kind: 'switch',
breakBlock,
label,
});
@@ -490,15 +501,15 @@ export default class HIRBuilder {
const last = this.#scopes.pop();
CompilerError.invariant(
last != null &&
last.kind === "switch" &&
last.kind === 'switch' &&
last.label === label &&
last.breakBlock === breakBlock,
{
reason: "Mismatched label",
reason: 'Mismatched label',
description: null,
loc: null,
suggestions: null,
}
},
);
return value;
}
@@ -513,10 +524,10 @@ export default class HIRBuilder {
continueBlock: BlockId,
// block following the loop. "break" jumps here.
breakBlock: BlockId,
fn: () => T
fn: () => T,
): T {
this.#scopes.push({
kind: "loop",
kind: 'loop',
label,
continueBlock,
breakBlock,
@@ -525,16 +536,16 @@ export default class HIRBuilder {
const last = this.#scopes.pop();
CompilerError.invariant(
last != null &&
last.kind === "loop" &&
last.kind === 'loop' &&
last.label === label &&
last.continueBlock === continueBlock &&
last.breakBlock === breakBlock,
{
reason: "Mismatched loops",
reason: 'Mismatched loops',
description: null,
loc: null,
suggestions: null,
}
},
);
return value;
}
@@ -548,14 +559,14 @@ export default class HIRBuilder {
const scope = this.#scopes[ii];
if (
(label === null &&
(scope.kind === "loop" || scope.kind === "switch")) ||
(scope.kind === 'loop' || scope.kind === 'switch')) ||
label === scope.label
) {
return scope.breakBlock;
}
}
CompilerError.invariant(false, {
reason: "Expected a loop or switch to be in scope",
reason: 'Expected a loop or switch to be in scope',
description: null,
loc: null,
suggestions: null,
@@ -570,13 +581,13 @@ export default class HIRBuilder {
lookupContinue(label: string | null): BlockId {
for (let ii = this.#scopes.length - 1; ii >= 0; ii--) {
const scope = this.#scopes[ii];
if (scope.kind === "loop") {
if (scope.kind === 'loop') {
if (label === null || label === scope.label) {
return scope.continueBlock;
}
} else if (label !== null && scope.label === label) {
CompilerError.invariant(false, {
reason: "Continue may only refer to a labeled loop",
reason: 'Continue may only refer to a labeled loop',
description: null,
loc: null,
suggestions: null,
@@ -584,7 +595,7 @@ export default class HIRBuilder {
}
}
CompilerError.invariant(false, {
reason: "Expected a loop to be in scope",
reason: 'Expected a loop to be in scope',
description: null,
loc: null,
suggestions: null,
@@ -633,7 +644,7 @@ function _shrink(func: HIR): void {
}
reachable.add(blockId);
const block = func.blocks.get(blockId)!;
block.terminal = mapTerminalSuccessors(block.terminal, (prevTarget) => {
block.terminal = mapTerminalSuccessors(block.terminal, prevTarget => {
const target = resolveBlockTarget(prevTarget);
queue.push(target);
return target;
@@ -649,7 +660,7 @@ function _shrink(func: HIR): void {
export function removeUnreachableForUpdates(fn: HIR): void {
for (const [, block] of fn.blocks) {
if (
block.terminal.kind === "for" &&
block.terminal.kind === 'for' &&
block.terminal.update !== null &&
!fn.blocks.has(block.terminal.update)
) {
@@ -670,10 +681,10 @@ export function removeDeadDoWhileStatements(func: HIR): void {
* MergeConsecutiveBlocks figures out how to merge as appropriate.
*/
for (const [_, block] of func.blocks) {
if (block.terminal.kind === "do-while") {
if (block.terminal.kind === 'do-while') {
if (!visited.has(block.terminal.test)) {
block.terminal = {
kind: "goto",
kind: 'goto',
block: block.terminal.loop,
variant: GotoVariant.Break,
id: block.terminal.id,
@@ -700,7 +711,7 @@ export function reversePostorderBlocks(func: HIR): void {
* may be in the output: blocks will be removed in the case of unreachable code in
* the input.
*/
function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
const visited: Set<BlockId> = new Set();
const used: Set<BlockId> = new Set();
const usedFallthroughs: Set<BlockId> = new Set();
@@ -772,7 +783,7 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
...block,
instructions: [],
terminal: {
kind: "unreachable",
kind: 'unreachable',
id: block.terminal.id,
loc: block.terminal.loc,
},
@@ -813,7 +824,7 @@ export function markPredecessors(func: HIR): void {
return;
}
CompilerError.invariant(block != null, {
reason: "unexpected missing block",
reason: 'unexpected missing block',
description: `block ${blockId}`,
loc: GeneratedSource,
});
@@ -826,7 +837,7 @@ export function markPredecessors(func: HIR): void {
}
visited.add(blockId);
const { terminal } = block;
const {terminal} = block;
for (const successor of eachTerminalSuccessor(terminal)) {
visit(successor, block);
@@ -841,7 +852,7 @@ export function markPredecessors(func: HIR): void {
*/
function getTargetIfIndirection(block: BasicBlock): number | null {
return block.instructions.length === 0 &&
block.terminal.kind === "goto" &&
block.terminal.kind === 'goto' &&
block.terminal.variant === GotoVariant.Break
? block.terminal.block
: null;
@@ -854,14 +865,14 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
export function removeUnnecessaryTryCatch(fn: HIR): void {
for (const [, block] of fn.blocks) {
if (
block.terminal.kind === "try" &&
block.terminal.kind === 'try' &&
!fn.blocks.has(block.terminal.handler)
) {
const handlerId = block.terminal.handler;
const fallthroughId = block.terminal.fallthrough;
const fallthrough = fn.blocks.get(fallthroughId);
block.terminal = {
kind: "goto",
kind: 'goto',
block: block.terminal.block,
id: makeInstructionId(0),
loc: block.terminal.loc,
@@ -882,13 +893,13 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
export function createTemporaryPlace(
env: Environment,
loc: SourceLocation
loc: SourceLocation,
): Place {
return {
kind: "Identifier",
kind: 'Identifier',
identifier: {
id: env.nextIdentifierId,
mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) },
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
name: null,
scope: null,
type: makeType(),

View File

@@ -5,16 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
import { CompilerError } from "../CompilerError";
import {CompilerError} from '../CompilerError';
import {
BlockId,
Effect,
GeneratedSource,
HIRFunction,
Instruction,
} from "./HIR";
import { markPredecessors } from "./HIRBuilder";
import { terminalFallthrough, terminalHasFallthrough } from "./visitors";
} from './HIR';
import {markPredecessors} from './HIRBuilder';
import {terminalFallthrough, terminalHasFallthrough} from './visitors';
/*
* Merges sequences of blocks that will always execute consecutively —
@@ -39,8 +39,8 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
for (const instr of block.instructions) {
if (
instr.value.kind === "FunctionExpression" ||
instr.value.kind === "ObjectMethod"
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
mergeConsecutiveBlocks(instr.value.loweredFunc.func);
}
@@ -50,7 +50,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
// Can only merge blocks with a single predecessor
block.preds.size !== 1 ||
// Value blocks cannot merge
block.kind !== "block" ||
block.kind !== 'block' ||
// Merging across fallthroughs could move the predecessor out of its block scope
fallthroughBlocks.has(block.id)
) {
@@ -65,7 +65,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
loc: null,
suggestions: null,
});
if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
/*
* The predecessor is not guaranteed to transfer control to this block,
* they aren't consecutive.
@@ -85,16 +85,16 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
const instr: Instruction = {
id: predecessor.terminal.id,
lvalue: {
kind: "Identifier",
kind: 'Identifier',
identifier: phi.id,
effect: Effect.ConditionallyMutate,
reactive: false,
loc: GeneratedSource,
},
value: {
kind: "LoadLocal",
kind: 'LoadLocal',
place: {
kind: "Identifier",
kind: 'Identifier',
identifier: operand,
effect: Effect.Read,
reactive: false,
@@ -113,7 +113,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
fn.body.blocks.delete(block.id);
}
markPredecessors(fn.body);
for (const [, { terminal }] of fn.body.blocks) {
for (const [, {terminal}] of fn.body.blocks) {
if (terminalHasFallthrough(terminal)) {
terminal.fallthrough = merged.get(terminal.fallthrough);
}

View File

@@ -4,16 +4,16 @@ import {
Place,
ReactiveScope,
makeInstructionId,
} from ".";
import { getPlaceScope } from "../ReactiveScopes/BuildReactiveBlocks";
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
import DisjointSet from "../Utils/DisjointSet";
import { getOrInsertDefault } from "../Utils/utils";
} from '.';
import {getPlaceScope} from '../ReactiveScopes/BuildReactiveBlocks';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import DisjointSet from '../Utils/DisjointSet';
import {getOrInsertDefault} from '../Utils/utils';
import {
eachInstructionLValue,
eachInstructionOperand,
eachTerminalOperand,
} from "./visitors";
} from './visitors';
/**
* While previous passes ensure that reactive scopes span valid sets of program
@@ -113,10 +113,10 @@ export function mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void {
joinedScopes.forEach((scope, groupScope) => {
if (scope !== groupScope) {
groupScope.range.start = makeInstructionId(
Math.min(groupScope.range.start, scope.range.start)
Math.min(groupScope.range.start, scope.range.start),
);
groupScope.range.end = makeInstructionId(
Math.max(groupScope.range.end, scope.range.end)
Math.max(groupScope.range.end, scope.range.end),
);
}
});
@@ -129,8 +129,8 @@ export function mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void {
}
type ScopeInfo = {
scopeStarts: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
scopeEnds: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
scopeStarts: Array<{id: InstructionId; scopes: Set<ReactiveScope>}>;
scopeEnds: Array<{id: InstructionId; scopes: Set<ReactiveScope>}>;
placeScopes: Map<Place, ReactiveScope>;
};
@@ -150,7 +150,7 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
placeScopes.set(place, scope);
if (scope.range.start !== scope.range.end) {
getOrInsertDefault(scopeStarts, scope.range.start, new Set()).add(
scope
scope,
);
getOrInsertDefault(scopeEnds, scope.range.end, new Set()).add(scope);
}
@@ -173,10 +173,10 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
return {
scopeStarts: [...scopeStarts.entries()]
.map(([id, scopes]) => ({ id, scopes }))
.map(([id, scopes]) => ({id, scopes}))
.sort((a, b) => b.id - a.id),
scopeEnds: [...scopeEnds.entries()]
.map(([id, scopes]) => ({ id, scopes }))
.map(([id, scopes]) => ({id, scopes}))
.sort((a, b) => b.id - a.id),
placeScopes,
};
@@ -184,8 +184,8 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
function visitInstructionId(
id: InstructionId,
{ scopeEnds, scopeStarts }: ScopeInfo,
{ activeScopes, joined }: TraversalState
{scopeEnds, scopeStarts}: ScopeInfo,
{activeScopes, joined}: TraversalState,
): void {
/**
* Handle all scopes that end at this instruction.
@@ -200,7 +200,7 @@ function visitInstructionId(
* order of start IDs because the scopes stack is ordered as such
*/
const scopesSortedStartDescending = [...scopeEndTop.scopes].sort(
(a, b) => b.range.start - a.range.start
(a, b) => b.range.start - a.range.start,
);
for (const scope of scopesSortedStartDescending) {
const idx = activeScopes.indexOf(scope);
@@ -227,7 +227,7 @@ function visitInstructionId(
scopeStarts.pop();
const scopesSortedEndDescending = [...scopeStartTop.scopes].sort(
(a, b) => b.range.end - a.range.end
(a, b) => b.range.end - a.range.end,
);
activeScopes.push(...scopesSortedEndDescending);
/**
@@ -247,14 +247,14 @@ function visitInstructionId(
function visitPlace(
id: InstructionId,
place: Place,
{ activeScopes, joined }: TraversalState
{activeScopes, joined}: TraversalState,
): void {
/**
* If an instruction mutates an outer scope, flatten all scopes from the top
* of the stack to the mutated outer scope.
*/
const placeScope = getPlaceScope(id, place);
if (placeScope != null && isMutable({ id } as any, place)) {
if (placeScope != null && isMutable({id} as any, place)) {
const placeScopeIdx = activeScopes.indexOf(placeScope);
if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) {
joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]);
@@ -264,7 +264,7 @@ function visitPlace(
function getOverlappingReactiveScopes(
fn: HIRFunction,
context: ScopeInfo
context: ScopeInfo,
): DisjointSet<ReactiveScope> {
const state: TraversalState = {
joined: new DisjointSet<ReactiveScope>(),

View File

@@ -5,15 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/
import { CompilerError } from "../CompilerError";
import { Effect, ValueKind, ValueReason } from "./HIR";
import {CompilerError} from '../CompilerError';
import {Effect, ValueKind, ValueReason} from './HIR';
import {
BuiltInType,
FunctionType,
ObjectType,
PolyType,
PrimitiveType,
} from "./Types";
} from './Types';
/*
* This file exports types and defaults for JavaScript object shapes. These are
@@ -22,7 +22,7 @@ import {
*/
const PRIMITIVE_TYPE: PrimitiveType = {
kind: "Primitive",
kind: 'Primitive',
};
let nextAnonId = 0;
@@ -42,8 +42,8 @@ function createAnonId(): string {
export function addFunction(
registry: ShapeRegistry,
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, "hookKind">,
id: string | null = null
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, {
@@ -51,7 +51,7 @@ export function addFunction(
hookKind: null,
});
return {
kind: "Function",
kind: 'Function',
return: fn.returnType,
shapeId,
};
@@ -64,13 +64,13 @@ export function addFunction(
*/
export function addHook(
registry: ShapeRegistry,
fn: FunctionSignature & { hookKind: HookKind },
id: string | null = null
fn: FunctionSignature & {hookKind: HookKind},
id: string | null = null,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, [], fn);
return {
kind: "Function",
kind: 'Function',
return: fn.returnType,
shapeId,
};
@@ -84,12 +84,12 @@ export function addHook(
export function addObject(
registry: ShapeRegistry,
id: string | null,
properties: Iterable<[string, BuiltInType | PolyType]>
properties: Iterable<[string, BuiltInType | PolyType]>,
): ObjectType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, null);
return {
kind: "Object",
kind: 'Object',
shapeId,
};
}
@@ -98,7 +98,7 @@ function addShape(
registry: ShapeRegistry,
id: string,
properties: Iterable<[string, BuiltInType | PolyType]>,
functionType: FunctionSignature | null
functionType: FunctionSignature | null,
): ObjectShape {
const shape: ObjectShape = {
properties: new Map(properties),
@@ -116,17 +116,17 @@ function addShape(
}
export type HookKind =
| "useContext"
| "useState"
| "useActionState"
| "useReducer"
| "useRef"
| "useEffect"
| "useLayoutEffect"
| "useInsertionEffect"
| "useMemo"
| "useCallback"
| "Custom";
| 'useContext'
| 'useState'
| 'useActionState'
| 'useReducer'
| 'useRef'
| 'useEffect'
| 'useLayoutEffect'
| 'useInsertionEffect'
| 'useMemo'
| 'useCallback'
| 'Custom';
/*
* Call signature of a function, used for type and effect inference.
@@ -190,91 +190,91 @@ export type ObjectShape = {
* the inferred types for [] and {}.
*/
export type ShapeRegistry = Map<string, ObjectShape>;
export const BuiltInPropsId = "BuiltInProps";
export const BuiltInArrayId = "BuiltInArray";
export const BuiltInFunctionId = "BuiltInFunction";
export const BuiltInJsxId = "BuiltInJsx";
export const BuiltInObjectId = "BuiltInObject";
export const BuiltInUseStateId = "BuiltInUseState";
export const BuiltInSetStateId = "BuiltInSetState";
export const BuiltInUseActionStateId = "BuiltInUseActionState";
export const BuiltInSetActionStateId = "BuiltInSetActionState";
export const BuiltInUseRefId = "BuiltInUseRefId";
export const BuiltInRefValueId = "BuiltInRefValue";
export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";
export const BuiltInUseEffectHookId = "BuiltInUseEffectHook";
export const BuiltInUseLayoutEffectHookId = "BuiltInUseLayoutEffectHook";
export const BuiltInUseInsertionEffectHookId = "BuiltInUseInsertionEffectHook";
export const BuiltInUseOperatorId = "BuiltInUseOperator";
export const BuiltInUseReducerId = "BuiltInUseReducer";
export const BuiltInDispatchId = "BuiltInDispatch";
export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
export const BuiltInUseStateId = 'BuiltInUseState';
export const BuiltInSetStateId = 'BuiltInSetState';
export const BuiltInUseActionStateId = 'BuiltInUseActionState';
export const BuiltInSetActionStateId = 'BuiltInSetActionState';
export const BuiltInUseRefId = 'BuiltInUseRefId';
export const BuiltInRefValueId = 'BuiltInRefValue';
export const BuiltInMixedReadonlyId = 'BuiltInMixedReadonly';
export const BuiltInUseEffectHookId = 'BuiltInUseEffectHook';
export const BuiltInUseLayoutEffectHookId = 'BuiltInUseLayoutEffectHook';
export const BuiltInUseInsertionEffectHookId = 'BuiltInUseInsertionEffectHook';
export const BuiltInUseOperatorId = 'BuiltInUseOperator';
export const BuiltInUseReducerId = 'BuiltInUseReducer';
export const BuiltInDispatchId = 'BuiltInDispatch';
// ShapeRegistry with default definitions for built-ins.
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
// If the `ref` prop exists, it has the ref type
addObject(BUILTIN_SHAPES, BuiltInPropsId, [
["ref", { kind: "Object", shapeId: BuiltInUseRefId }],
['ref', {kind: 'Object', shapeId: BuiltInUseRefId}],
]);
/* Built-in array shape */
addObject(BUILTIN_SHAPES, BuiltInArrayId, [
[
"indexOf",
'indexOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"includes",
'includes',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
"pop",
'pop',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
[
"at",
'at',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
"concat",
'concat',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
returnType: {
kind: "Object",
kind: 'Object',
shapeId: BuiltInArrayId,
},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
["length", PRIMITIVE_TYPE],
['length', PRIMITIVE_TYPE],
[
"push",
'push',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
@@ -284,12 +284,12 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"slice",
'slice',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {
kind: "Object",
kind: 'Object',
shapeId: BuiltInArrayId,
},
calleeEffect: Effect.Capture,
@@ -297,11 +297,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"map",
'map',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
@@ -314,11 +314,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"filter",
'filter',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
@@ -331,11 +331,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"every",
'every',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
@@ -348,11 +348,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"some",
'some',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
@@ -365,11 +365,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"find",
'find',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
@@ -377,11 +377,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"findIndex",
'findIndex',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
/*
* callee is ConditionallyMutate because items of the array
* flow into the lambda and may be mutated there, even though
@@ -394,7 +394,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
}),
],
[
"join",
'join',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
@@ -409,7 +409,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
/* Built-in Object shape */
addObject(BUILTIN_SHAPES, BuiltInObjectId, [
[
"toString",
'toString',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
@@ -425,9 +425,9 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
["0", { kind: "Poly" }],
['0', {kind: 'Poly'}],
[
"1",
'1',
addFunction(
BUILTIN_SHAPES,
[],
@@ -438,15 +438,15 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
},
BuiltInSetStateId
BuiltInSetStateId,
),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
["0", { kind: "Poly" }],
['0', {kind: 'Poly'}],
[
"1",
'1',
addFunction(
BUILTIN_SHAPES,
[],
@@ -457,15 +457,15 @@ addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
},
BuiltInSetActionStateId
BuiltInSetActionStateId,
),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [
["0", { kind: "Poly" }],
['0', {kind: 'Poly'}],
[
"1",
'1',
addFunction(
BUILTIN_SHAPES,
[],
@@ -476,22 +476,22 @@ addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
},
BuiltInDispatchId
BuiltInDispatchId,
),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseRefId, [
["current", { kind: "Object", shapeId: BuiltInRefValueId }],
['current', {kind: 'Object', shapeId: BuiltInRefValueId}],
]);
addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
["*", { kind: "Object", shapeId: BuiltInRefValueId }],
['*', {kind: 'Object', shapeId: BuiltInRefValueId}],
]);
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
[
"toString",
'toString',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
@@ -501,34 +501,34 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"map",
'map',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
],
[
"filter",
'filter',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: { kind: "Object", shapeId: BuiltInArrayId },
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
],
[
"concat",
'concat',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
returnType: {
kind: "Object",
kind: 'Object',
shapeId: BuiltInArrayId,
},
calleeEffect: Effect.Capture,
@@ -536,12 +536,12 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"slice",
'slice',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {
kind: "Object",
kind: 'Object',
shapeId: BuiltInArrayId,
},
calleeEffect: Effect.Capture,
@@ -549,11 +549,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"every",
'every',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
@@ -561,11 +561,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"some",
'some',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
@@ -573,11 +573,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"find",
'find',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Mutable,
noAlias: true,
@@ -585,11 +585,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"findIndex",
'findIndex',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Primitive" },
returnType: {kind: 'Primitive'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
@@ -597,7 +597,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
}),
],
[
"join",
'join',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
@@ -606,7 +606,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
returnValueKind: ValueKind.Primitive,
}),
],
["*", { kind: "Object", shapeId: BuiltInMixedReadonlyId }],
['*', {kind: 'Object', shapeId: BuiltInMixedReadonlyId}],
]);
addObject(BUILTIN_SHAPES, BuiltInJsxId, []);
@@ -617,12 +617,12 @@ export const DefaultMutatingHook = addHook(
{
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "Custom",
hookKind: 'Custom',
returnValueKind: ValueKind.Mutable,
},
"DefaultMutatingHook"
'DefaultMutatingHook',
);
export const DefaultNonmutatingHook = addHook(
@@ -630,10 +630,10 @@ export const DefaultNonmutatingHook = addHook(
{
positionalParams: [],
restParam: Effect.Freeze,
returnType: { kind: "Poly" },
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
hookKind: "Custom",
hookKind: 'Custom',
returnValueKind: ValueKind.Frozen,
},
"DefaultNonmutatingHook"
'DefaultNonmutatingHook',
);

View File

@@ -1,5 +1,5 @@
import { CompilerError } from "..";
import { BlockId, GotoVariant, HIRFunction } from "./HIR";
import {CompilerError} from '..';
import {BlockId, GotoVariant, HIRFunction} from './HIR';
export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
const merged: Array<{
@@ -10,16 +10,16 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
const rewrites: Map<BlockId, BlockId> = new Map();
for (const [blockId, block] of fn.body.blocks) {
const terminal = block.terminal;
if (terminal.kind === "label") {
const { block: nextId, fallthrough: fallthroughId } = terminal;
if (terminal.kind === 'label') {
const {block: nextId, fallthrough: fallthroughId} = terminal;
const next = fn.body.blocks.get(nextId)!;
const fallthrough = fn.body.blocks.get(fallthroughId)!;
if (
next.terminal.kind === "goto" &&
next.terminal.kind === 'goto' &&
next.terminal.variant === GotoVariant.Break &&
next.terminal.block === fallthroughId
) {
if (next.kind === "block" && fallthrough.kind === "block") {
if (next.kind === 'block' && fallthrough.kind === 'block') {
// Only merge normal block types
merged.push({
label: blockId,
@@ -45,9 +45,9 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
CompilerError.invariant(
next.phis.size === 0 && fallthrough.phis.size === 0,
{
reason: "Unexpected phis when merging label blocks",
reason: 'Unexpected phis when merging label blocks',
loc: label.terminal.loc,
}
},
);
CompilerError.invariant(
@@ -56,9 +56,9 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
next.preds.has(originalLabelId) &&
fallthrough.preds.has(nextId),
{
reason: "Unexpected block predecessors when merging label blocks",
reason: 'Unexpected block predecessors when merging label blocks',
loc: label.terminal.loc,
}
},
);
label.instructions.push(...next.instructions, ...fallthrough.instructions);

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { CompilerError } from "../CompilerError";
import {CompilerError} from '../CompilerError';
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
@@ -16,7 +16,7 @@ export type Type =
| PolyType
| PropType
| ObjectMethod;
export type PrimitiveType = { kind: "Primitive" };
export type PrimitiveType = {kind: 'Primitive'};
/*
* An {@link FunctionType} or {@link ObjectType} (also a JS object) may be associated with an
@@ -34,36 +34,36 @@ export type PrimitiveType = { kind: "Primitive" };
*/
export type FunctionType = {
kind: "Function";
kind: 'Function';
shapeId: string | null;
return: Type;
};
export type ObjectType = {
kind: "Object";
kind: 'Object';
shapeId: string | null;
};
export type TypeVar = {
kind: "Type";
kind: 'Type';
id: TypeId;
};
export type PolyType = {
kind: "Poly";
kind: 'Poly';
};
export type PhiType = {
kind: "Phi";
kind: 'Phi';
operands: Array<Type>;
};
export type PropType = {
kind: "Property";
kind: 'Property';
objectType: Type;
objectName: string;
propertyName: string;
};
export type ObjectMethod = {
kind: "ObjectMethod";
kind: 'ObjectMethod';
};
/*
@@ -71,11 +71,11 @@ export type ObjectMethod = {
* accidentally.
*/
const opaqueTypeId = Symbol();
export type TypeId = number & { [opaqueTypeId]: "IdentifierId" };
export type TypeId = number & {[opaqueTypeId]: 'IdentifierId'};
export function makeTypeId(id: number): TypeId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: "Expected instruction id to be a non-negative integer",
reason: 'Expected instruction id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
@@ -86,7 +86,7 @@ export function makeTypeId(id: number): TypeId {
let typeCounter = 0;
export function makeType(): TypeVar {
return {
kind: "Type",
kind: 'Type',
id: makeTypeId(typeCounter++),
};
}
@@ -97,40 +97,40 @@ export function makeType(): TypeVar {
*/
export function duplicateType(type: Type): Type {
switch (type.kind) {
case "Function": {
case 'Function': {
return {
kind: "Function",
kind: 'Function',
return: duplicateType(type.return),
shapeId: type.shapeId,
};
}
case "Object": {
return { kind: "Object", shapeId: type.shapeId };
case 'Object': {
return {kind: 'Object', shapeId: type.shapeId};
}
case "ObjectMethod": {
return { kind: "ObjectMethod" };
case 'ObjectMethod': {
return {kind: 'ObjectMethod'};
}
case "Phi": {
case 'Phi': {
return {
kind: "Phi",
operands: type.operands.map((operand) => duplicateType(operand)),
kind: 'Phi',
operands: type.operands.map(operand => duplicateType(operand)),
};
}
case "Poly": {
return { kind: "Poly" };
case 'Poly': {
return {kind: 'Poly'};
}
case "Primitive": {
return { kind: "Primitive" };
case 'Primitive': {
return {kind: 'Primitive'};
}
case "Property": {
case 'Property': {
return {
kind: "Property",
kind: 'Property',
objectType: duplicateType(type.objectType),
objectName: type.objectName,
propertyName: type.propertyName,
};
}
case "Type": {
case 'Type': {
return makeType();
}
}
@@ -151,7 +151,7 @@ export function typeEquals(tA: Type, tB: Type): boolean {
}
function typeVarEquals(tA: Type, tB: Type): boolean {
if (tA.kind === "Type" && tB.kind === "Type") {
if (tA.kind === 'Type' && tB.kind === 'Type') {
return tA.id === tB.id;
}
return false;
@@ -162,11 +162,11 @@ function typeKindCheck(tA: Type, tb: Type, type: string): boolean {
}
function objectMethodTypeEquals(tA: Type, tB: Type): boolean {
return typeKindCheck(tA, tB, "ObjectMethod");
return typeKindCheck(tA, tB, 'ObjectMethod');
}
function propTypeEquals(tA: Type, tB: Type): boolean {
if (tA.kind === "Property" && tB.kind === "Property") {
if (tA.kind === 'Property' && tB.kind === 'Property') {
if (!typeEquals(tA.objectType, tB.objectType)) {
return false;
}
@@ -180,15 +180,15 @@ function propTypeEquals(tA: Type, tB: Type): boolean {
}
function primitiveTypeEquals(tA: Type, tB: Type): boolean {
return typeKindCheck(tA, tB, "Primitive");
return typeKindCheck(tA, tB, 'Primitive');
}
function polyTypeEquals(tA: Type, tB: Type): boolean {
return typeKindCheck(tA, tB, "Poly");
return typeKindCheck(tA, tB, 'Poly');
}
function objectTypeEquals(tA: Type, tB: Type): boolean {
if (tA.kind === "Object" && tB.kind == "Object") {
if (tA.kind === 'Object' && tB.kind == 'Object') {
return tA.shapeId === tB.shapeId;
}
@@ -196,14 +196,14 @@ function objectTypeEquals(tA: Type, tB: Type): boolean {
}
function funcTypeEquals(tA: Type, tB: Type): boolean {
if (tA.kind !== "Function" || tB.kind !== "Function") {
if (tA.kind !== 'Function' || tB.kind !== 'Function') {
return false;
}
return typeEquals(tA.return, tB.return);
}
function phiTypeEquals(tA: Type, tB: Type): boolean {
if (tA.kind === "Phi" && tB.kind === "Phi") {
if (tA.kind === 'Phi' && tB.kind === 'Phi') {
if (tA.operands.length !== tB.operands.length) {
return false;
}

View File

@@ -5,16 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
export { assertConsistentIdentifiers } from "./AssertConsistentIdentifiers";
export {assertConsistentIdentifiers} from './AssertConsistentIdentifiers';
export {
assertTerminalSuccessorsExist,
assertTerminalPredsExist,
} from "./AssertTerminalBlocksExist";
export { assertValidBlockNesting } from "./AssertValidBlockNesting";
export { assertValidMutableRanges } from "./AssertValidMutableRanges";
export { lower } from "./BuildHIR";
export { buildReactiveScopeTerminalsHIR } from "./BuildReactiveScopeTerminalsHIR";
export { computeDominatorTree, computePostDominatorTree } from "./Dominator";
} from './AssertTerminalBlocksExist';
export {assertValidBlockNesting} from './AssertValidBlockNesting';
export {assertValidMutableRanges} from './AssertValidMutableRanges';
export {lower} from './BuildHIR';
export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR';
export {computeDominatorTree, computePostDominatorTree} from './Dominator';
export {
Environment,
parseConfigPragma,
@@ -22,15 +22,15 @@ export {
type EnvironmentConfig,
type ExternalFunction,
type Hook,
} from "./Environment";
export * from "./HIR";
} from './Environment';
export * from './HIR';
export {
markInstructionIds,
markPredecessors,
removeUnnecessaryTryCatch,
reversePostorderBlocks,
} from "./HIRBuilder";
export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
export { mergeOverlappingReactiveScopesHIR } from "./MergeOverlappingReactiveScopesHIR";
export { printFunction, printHIR } from "./PrintHIR";
export { pruneUnusedLabelsHIR } from "./PruneUnusedLabelsHIR";
} from './HIRBuilder';
export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks';
export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR';
export {printFunction, printHIR} from './PrintHIR';
export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR';

Some files were not shown because too many files have changed in this diff Show More