Skip to content

React usage check: address review feedback - #2

Merged
gunjanjaswal merged 12 commits into
gunjanjaswal:add/inlined-react-runtime-checkfrom
jsnajdr:feedback/react-usage-check
Sep 24, 2026
Merged

gunjanjaswal merged 12 commits into
gunjanjaswal:add/inlined-react-runtime-checkfrom
jsnajdr:feedback/react-usage-check

Conversation

@jsnajdr

@jsnajdr jsnajdr commented Sep 24, 2026

Copy link
Copy Markdown

Addresses review feedback in WordPress#1380 (comment)

The check is about compatibility, not performance: all but one of its
results are about code that stops working when WordPress upgrades React.
Only the development-build note is about size and speed.

Follows the directory convention of the other checks, where the directory
and namespace mirror the primary category.
The replacement was substituted into the translated sentence through a
placeholder, which works for the APIs whose replacement is a code
identifier but not for the two that need prose: "a ref on the element"
and "JSX or createElement()" reached the user in English whatever the
locale.

Give those two the complete sentence as their own translatable string and
keep the shared one for the replacements that are code. The reported text
is unchanged.
2.0.0 and 2.1.0 are both released, so @SInCE 2.0.0 claimed the check had
been available for two releases that never contained it. The next release
is 2.2.0.

Drop the changelog entry as well. It was inserted into the released 2.0.0
section, and per docs/releasing.md the changelog is written when the
release is cut, which is why the other checks added since 2.1.0 do not
carry one.
Comments and string literals were blanked with a single regular
expression, which failed in two ways.

It could not tell a regex literal from a division, so the quote in
`var re = /"/g;` opened what looked like a string and blanked the code
after it. Any removed API called later in such a file went unreported.

PCRE also exhausted its recursion limit on the long string literals of a
bundled file. preg_replace_callback then returned null and the fallback
handed back the raw contents, so nothing was blanked at all and a mention
in a comment was reported as a call, undoing what blanking is for.

Walk the contents token by token instead. A slash opens a regular
expression where no value precedes it and divides otherwise, which also
covers `return/^a$/` and a slash inside a character class. There is no
backtracking, so file size no longer matters: a 3 MB literal that used to
defeat PCRE now scans in well under a second.
The fingerprints for `react/jsx-runtime` and `react` matched too readily
once the file mentioned the element symbol for an unrelated reason, which
`react-is` does and which reaches a great many bundles through
`prop-types`. `jsxs?\s*[:=]` then matched a syntax highlighter defining
`Prism.languages.jsx`, or a parser option object holding `jsx: true`, and
reported an error against a plugin with no React problem at all.

The same applies to `preact/compat`, which names the element symbol and
exports React's internals sentinel while creating Preact vnodes. It never
touches the React WordPress ships, yet it was reported as inlining react.

Require `_owner`, a field React 19 dropped from the element object, so a
package is only implicated when the file also builds pre-19 elements. The
renderer is exempt: it consumes elements rather than creating them, so a
file holding only a copy of `react-dom` has no factory, and `__reactFiber$`
is specific enough by itself. That exemption also stops such a file from
being reported as inlining react as well as react-dom.

Verified against react, react-dom and react/jsx-runtime for 16, 17 and 18
in both development and production, and against the bundles of astra-sites
and wp-table-builder, all of which are still detected.
Positions were counted by splitting on PHP_EOL, the line ending native to
the machine running the check. A plugin's line endings have nothing to do
with that, so a file written with line feeds collapsed onto line 1 when
checked on Windows, and a file written with carriage returns alone
collapsed onto line 1 everywhere.

Split on all three line endings instead, counting a carriage return
followed by a line feed once. The regression fixture uses carriage
returns alone, the case that was wrong on every platform and the one
line ending git will not rewrite on checkout.
The guard accepted any mention of window.React as proof that the package
was externalized, reads and writes alike. A build that publishes itself
under the global therefore silenced the very error it should raise: it
can only publish a copy it carries, and it replaces the copy WordPress
loaded for every script that runs after it.

Take a read of the global as proof of externalizing, and let an
assignment anywhere in the file override every read in it.

The packages now carry the name of their global rather than a pattern,
since two patterns are built from it. fiber-inspector.js restores
coverage of the read: it is now the only file among those expected to
stay silent whose silence depends on the guard.
The marker that establishes a pre-19 build is the name of the element
symbol, which was matched only between quotes. Some minifiers rewrite
every string in a bundle as a template literal, leaving a build with no
quoted string in it at all, and such a file passed as having no React
inlined.

Accept the backtick alongside the two quotes. Only this marker read a
delimiter; the rest match bare substrings.
The bundles WordPress ships patch React 19 into accepting the pre-19
element shape and warning about it, so whether an inlined copy fails
outright depends on where its elements end up.

Hedge the claim to match, and record in the class docblock why the
severity is still an error: the patch is there to carry plugins through
the upgrade, not to make the older shape supported.

The removed-API warnings keep saying such a call stops working, because
those APIs are gone from React 19 with nothing standing in for them.
The comment justified the skip for an inlined renderer, where a removed
API found in the file is React's own code, but not for the other two
packages, where the call may well be the plugin's own.

Record the rest of the reasoning: a deprecated call is a small thing to
raise beside a bundled copy of React, and it is not lost, because the
marker goes when the inlined copy does and the next run reports it.
@jsnajdr

jsnajdr commented Sep 24, 2026

Copy link
Copy Markdown
Author

@gunjanjaswal This is another batch of improvements for the main PR. Please have a look when you have time.

@jsnajdr
jsnajdr force-pushed the feedback/react-usage-check branch from ccabd5e to 43d7f0b Compare September 24, 2026 09:23
@jsnajdr

jsnajdr commented Sep 24, 2026

Copy link
Copy Markdown
Author

I decided to revert the new react-is check, as it doesn't have much effect in practice. It's used mostly by prop-types, where it doesn't really break anything when an optional (dev only) validation fails.

The check graded a development build above a production one, but nothing
recorded why one should outrank the other, and the numbers were close
enough to a submission threshold to matter. Report both at the default
severity and let the message carry the difference.
A development build was detected by the documentation links React embeds
in its warnings, and reported with its own wording. Inlining the package
is the problem either way, so the second message and the sniffing behind
it bought a branch through the reporting path and little else.
@gunjanjaswal
gunjanjaswal merged commit 495f50a into gunjanjaswal:add/inlined-react-runtime-check Sep 24, 2026
@gunjanjaswal

Copy link
Copy Markdown
Owner

Merged, thanks — this is a big tightening. The _owner: element-factory requirement is a clean way to rule out the Prism jsx: and preact/compat false positives, and swapping the blank-out regexp for a token scanner removes a whole class of edge cases. Agree on keeping bundled React as an error with "will likely break" wording, and on moving it to General. It's all in WordPress#1380 now.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants