* Revise ESLint rules for string coercion
Currently, react uses `'' + value` to coerce mixed values to strings.
This code will throw for Temporal objects or symbols.
To make string-coercion safer and to improve user-facing error messages,
This commit adds a new ESLint rule called `safe-string-coercion`.
This rule has two modes: a production mode and a non-production mode.
* If the `isProductionUserAppCode` option is true, then `'' + value`
coercions are allowed (because they're faster, although they may
throw) and `String(value)` coercions are disallowed. Exception:
when building error messages or running DEV-only code in prod
files, `String()` should be used because it won't throw.
* If the `isProductionUserAppCode` option is false, then `'' + value`
coercions are disallowed (because they may throw, and in non-prod
code it's not worth the risk) and `String(value)` are allowed.
Production mode is used for all files which will be bundled with
developers' userland apps. Non-prod mode is used for all other React
code: tests, DEV blocks, devtools extension, etc.
In production mode, in addiiton to flagging `String(value)` calls,
the rule will also flag `'' + value` or `value + ''` coercions that may
throw. The rule is smart enough to silence itself in the following
"will never throw" cases:
* When the coercion is wrapped in a `typeof` test that restricts to safe
(non-symbol, non-object) types. Example:
if (typeof value === 'string' || typeof value === 'number') {
thisWontReport('' + value);
}
* When what's being coerced is a unary function result, because unary
functions never return an object or a symbol.
* When the coerced value is a commonly-used numeric identifier:
`i`, `idx`, or `lineNumber`.
* When the statement immeidately before the coercion is a DEV-only
call to a function from shared/CheckStringCoercion.js. This call is a
no-op in production, but in DEV it will show a console error
explaining the problem, then will throw right after a long explanatory
code comment so that debugger users will have an idea what's going on.
The check function call must be in the following format:
if (__DEV__) {
checkXxxxxStringCoercion(value);
};
Manually disabling the rule is usually not necessary because almost all
prod use of the `'' + value` pattern falls into one of the categories
above. But in the rare cases where the rule isn't smart enough to detect
safe usage (e.g. when a coercion is inside a nested ternary operator),
manually disabling the rule will be needed.
The rule should also be manually disabled in prod error handling code
where `String(value)` should be used for coercions, because it'd be
bad to throw while building an error message or stack trace!
The prod and non-prod modes have differentiated error messages to
explain how to do a proper coercion in that mode.
If a production check call is needed but is missing or incorrect
(e.g. not in a DEV block or not immediately before the coercion), then
a context-sensitive error message will be reported so that developers
can figure out what's wrong and how to fix the problem.
Because string coercions are now handled by the `safe-string-coercion`
rule, the `no-primitive-constructor` rule no longer flags `String()`
usage. It still flags `new String(value)` because that usage is almost
always a bug.
* Add DEV-only string coercion check functions
This commit adds DEV-only functions to check whether coercing
values to strings using the `'' + value` pattern will throw. If it will
throw, these functions will:
1. Display a console error with a friendly error message describing
the problem and the developer can fix it.
2. Perform the coercion, which will throw. Right before the line where
the throwing happens, there's a long code comment that will help
debugger users (or others looking at the exception call stack) figure
out what happened and how to fix the problem.
One of these check functions should be called before all string coercion
of user-provided values, except when the the coercion is guaranteed not
to throw, e.g.
* if inside a typeof check like `if (typeof value === 'string')`
* if coercing the result of a unary function like `+value` or `value++`
* if coercing a variable named in a whitelist of numeric identifiers:
`i`, `idx`, or `lineNumber`.
The new `safe-string-coercion` internal ESLint rule enforces that
these check functions are called when they are required.
Only use these check functions in production code that will be bundled
with user apps. For non-prod code (and for production error-handling
code), use `String(value)` instead which may be a little slower but will
never throw.
* Add failing tests for string coercion
Added failing tests to verify:
* That input, select, and textarea elements with value and defaultValue
set to Temporal-like objects which will throw when coerced to string
using the `'' + value` pattern.
* That text elements will throw for Temporal-like objects
* That dangerouslySetInnerHTML will *not* throw for Temporal-like
objects because this value is not cast to a string before passing to
the DOM.
* That keys that are Temporal-like objects will throw
All tests above validate the friendly error messages thrown.
* Use `String(value)` for coercion in non-prod files
This commit switches non-production code from `'' + value` (which
throws for Temporal objects and symbols) to instead use `String(value)`
which won't throw for these or other future plus-phobic types.
"Non-produciton code" includes anything not bundled into user apps:
* Tests and test utilities. Note that I didn't change legacy React
test fixtures because I assumed it was good for those files to
act just like old React, including coercion behavior.
* Build scripts
* Dev tools package - In addition to switching to `String`, I also
removed special-case code for coercing symbols which is now
unnecessary.
* Add DEV-only string coercion checks to prod files
This commit adds DEV-only function calls to to check if string coercion
using `'' + value` will throw, which it will if the value is a Temporal
object or a symbol because those types can't be added with `+`.
If it will throw, then in DEV these checks will show a console error
to help the user undertsand what went wrong and how to fix the
problem. After emitting the console error, the check functions will
retry the coercion which will throw with a call stack that's easy (or
at least easier!) to troubleshoot because the exception happens right
after a long comment explaining the issue. So whether the user is in
a debugger, looking at the browser console, or viewing the in-browser
DEV call stack, it should be easy to understand and fix the problem.
In most cases, the safe-string-coercion ESLint rule is smart enough to
detect when a coercion is safe. But in rare cases (e.g. when a coercion
is inside a ternary) this rule will have to be manually disabled.
This commit also switches error-handling code to use `String(value)`
for coercion, because it's bad to crash when you're trying to build
an error message or a call stack! Because `String()` is usually
disallowed by the `safe-string-coercion` ESLint rule in production
code, the rule must be disabled when `String()` is used.
* Remove pushEmpty
This is only used to support the assignID mechanism.
* Remove assignID mechanism
This effectively isn't used anyway because we always insert a dummy tag
into the fallback.
* Emit the template tag with an ID directly in pending boundaries
This ensures that assigning the ID is deterministic since it's done during
writing.
This also avoids emitting it for client rendered boundaries that start as
client rendered since we never need to refer to them.
* Move lazy ID initialization to the core implementation
We never need an ID before we write a pending boundary. This also ensures
that ID generation is deterministic by moving it to the write phase.
* Simplify the inserted scripts
We can assume that there are no text nodes before the template tag so this
simplifies the script that finds the comment node. It should be the direct
previous child.
Recoil uses useMutableSource behind a flag. I thought this was fine
because Recoil isn't used in any concurrent roots, so the behavior
would be the same, but it turns out that it is used by concurrent
roots in a few places.
I'm not expecting it to be hard to migrate to useSyncExternalStore, but
to de-risk the change I'm going to roll it out gradually with a flag. In
the meantime, I've added back the useMutableSource API.
Adds a third argument called `getServerSnapshot`.
On the server, React calls this one instead of the normal `getSnapshot`.
We also call it during hydration.
So it represents the snapshot that is used to generate the initial,
server-rendered HTML. The purpose is to avoid server-client mismatches.
What we render during hydration needs to match up exactly with what we
render on the server.
The pattern is for the server to send down a serialized copy of the
store that was used to generate the initial HTML. On the client, React
will call either `getSnapshot` or `getServerSnapshot` on the client as
appropriate, depending on whether it's currently hydrating.
The argument is optional for fully client rendered use cases. If the
user does attempt to omit `getServerSnapshot`, and the hook is called
on the server, React will abort that subtree on the server and
revert to client rendering, up to the nearest Suspense boundary.
For the userspace shim, we will need to use a heuristic (canUseDOM)
to determine whether we are in a server environment. I'll do that in
a follow up.
* Wire up DOM legacy build
* Hack to filter extra comments for testing purposes
* Use string concat in renderToString
I think this might be faster. We could probably use a combination of this
technique in the stream too to lower the overhead.
* Error if we can't complete the root synchronously
Maybe this should always error but in the async forms we can just delay
the stream until it resolves so it does have some useful semantics.
In the synchronous form it's never useful though. I'm mostly adding the
error because we're testing this behavior for renderToString specifically.
* Gate memory leak tests of internals
These tests don't translate as is to the new implementation and have been
ported to the Fizz tests separately.
* Enable Fizz legacy mode in stable
* Add wrapper around the ServerFormatConfig for legacy mode
This ensures that we can inject custom overrides without negatively
affecting the new implementation.
This adds another field for static mark up for example.
* Wrap pushTextInstance to avoid emitting comments for text in static markup
* Don't emit static mark up for completed suspense boundaries
Completed and client rendered boundaries are only marked for the client
to take over.
Pending boundaries are still supported in case you stream non-hydratable
mark up.
* Wire up generateStaticMarkup to static API entry points
* Mark as renderer for stable
This shouldn't affect the FB one ideally but it's done with the same build
so let's hope this works.
* Implement component stacks
This uses a reverse linked list in DEV-only to keep track of where we're
currently executing.
* Fix bug that wasn't picking up the right stack at suspended boundaries
This makes it more explicit which stack we pass in to be retained by the
task.
Typically we don't need to restore the context here because we assume that
we'll terminate the rest of the subtree so we don't need the correct
context since we're not rendering any siblings.
However, after a nested suspense boundary we need to restore the context.
The boundary could do this but since we're already doing this in the
suspense branch of renderNode, we might as well do it in the error case
which isn't very perf sensitive anyway.
* Fix reentrancy bug
* Fix another reentrancy bug
There's also an issue if we try to schedule something to be client
rendered if its fallback hasn't rendered yet. So we don't do it
in that case.
* Add NewContext module
This implements a reverse linked list tree containing the previous
contexts.
* Implement recursive algorithm
This algorithm pops the contexts back to a shared ancestor on the way down
the stack and then pushes new contexts in reverse order up the stack.
* Move isPrimaryRenderer to ServerFormatConfig
This is primarily intended to be used to support renderToString with a
separate build than the main one. This allows them to be nested.
* Wire up more element type matchers
* Wire up Context Provider type
* Wire up Context Consumer
* Test
* Implement reader in class
* Update error codez
* Split out into helper functions
This is similar to the structure of beginWork in Fiber.
* Split the rendering of a node from recursively rendering a node
This lets us reuse render node at the root which doesn't spawn new work.
* Remove redundant initial of isArray (#21163)
* Reapply prettier
* Type the isArray function with refinement support
This ensures that an argument gets refined just like it does if isArray is
used directly.
I'm not sure how to express with just a direct reference so I added a
function wrapper and confirmed that this does get inlined properly by
closure compiler.
* A few more
* Rename unit test to internal
This is not testing a bundle.
Co-authored-by: Behnam Mohammadi <itten@live.com>
* Implement DOM format config structure
* Styles
* Input warnings
* Textarea special cases
* Select special cases
* Option special cases
We read the currently selected value from the FormatContext.
* Warning for non-lower case HTML
We don't change to lower case at runtime anymore but keep the warning.
* Pre tags innerHTML needs to be prefixed
This is because if you do the equivalent on the client using innerHTML,
this is the effect you'd get.
* Extract errors
* Add onError option to Flight Server
The callback is called any time an error is generated in a server component.
This allows it to be logged on a server if needed. It'll still be rethrown
on the client so it can be logged there too but in case it never reaches
the client, here's a way to make sure it doesn't get lost.
* Add fatal error handling
* Encode tables as a special insertion mode
The table modes are special in that its children can't be created outside
a table context so we need the segment container to be wrapped in a table.
* Move formatContext from Task to Segment
It works the same otherwise. It's just that this context needs to outlive
the task so that I can use it when writing the segment.
* Use template tag for placeholders and inserted dummy nodes with IDs
These can be used in any parent. At least outside IE11. Not sure yet what
happens in IE11 to these.
Not sure if these are bad for perf since they're special nodes.
* Add special wrappers around inserted segments depending on their insertion mode
* Allow the root namespace to be configured
This allows us to insert the correct wrappers when streaming into an
existing non-HTML tree.
* Add comment
* Add format context
* Let the Work node hold all working state for the recursive loop
Stacks are nice and all but there's a cost to maintaining each frame
both in terms of stack size usage and writing to it.
* Move current format context into work
* Synchronously render children of a Suspense boundary
We don't have to spawn work and snapshot the context. Instead we can try
to render the boundary immediately in case it works.
* Lazily create the fallback work
Instead of eagerly create the fallback work and then immediately abort it.
We can just avoid creating it if we finish synchronously.
* Report errors to a global handler
This allows you to log errors or set things like status codes.
* Add complete callback
* onReadyToStream callback
This is typically not needed because if you want to stream when the
root is ready you can just start writing immediately.
* Rename onComplete -> onCompleteAll