diff --git a/README.md b/README.md index f40831f..5d4b680 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ PRs still in flight. Every row has a patch in `packages/` you can drop into your | :--- | :--- | :--- | :--- | | [`react-native`](packages/react-native/) | `0.85.3` | Set `:always_out_of_date => "1"` on `hermes-engine.podspec`'s `[Hermes] Replace Hermes for the right configuration, if needed` script_phase. The phase had no declared outputs (overwrites the prebuilt Hermes binary in place per `$CONFIGURATION`), so Xcode 14+ was warning about it on every clean build of every project on the default prebuilt-release-tarball Hermes path. Matches the family pattern from `React-Core-prebuilt.podspec` ([#52133](https://github.com/facebook/react-native/pull/52133)) and `ReactNativeDependencies.podspec` ([#49812](https://github.com/facebook/react-native/pull/49812)). | [facebook/react-native#56912](https://github.com/facebook/react-native/pull/56912) | | [`bun`](packages/oven-sh/bun/) | `1.3.14` | Drop the order-dependent peer-dep early-match block from `get_or_put_resolved_package` so `bun.lock` stops varying run to run. The block bound peers to whichever same-name resolution `package_index` held first, and `package_index` fills in thread-pool-completion order. Dedup and the "incorrect peer dependency" warning move into `Tree::hoist_dependency` where placement is deterministic. Rust port of Dylan's [#29804](https://github.com/oven-sh/bun/pull/29804). | [oven-sh/bun#30855](https://github.com/oven-sh/bun/pull/30855) | -| [`@react-native/babel-preset`](packages/@react-native/babel-preset/) | `0.85.3` | Three Babel plugins that rewrite source patterns Hermes V1 mishandles: `async ({a}) =>` (await resolves with `undefined` while the body keeps running), `class` inside `finally` (IR-cache contamination), and `super.x` in object-accessor identifier keys (segfaults at IR generation). Port of [@kitten](https://github.com/kitten)'s plugins from `babel-preset-expo` ([expo/expo#45601](https://github.com/expo/expo/pull/45601)) so bare RN consumers escape the bugs without `babel-preset-expo`. Root cause: [facebook/hermes#1761](https://github.com/facebook/hermes/issues/1761). | [facebook/react-native#56816](https://github.com/facebook/react-native/pull/56816) | | [`@convex-dev/better-auth`](packages/@convex-dev/better-auth/) | `0.12.2` | Wrap `fetchAccessToken` in `new Promise()` so `useConvexAuth().isAuthenticated` flips after sign-in on Hermes V1. The Expo SDK 56 canary dropped `@babel/plugin-transform-async-to-generator` from its Hermes V1 preset ([expo/expo#45345](https://github.com/expo/expo/pull/45345)), exposing a bridge race the transform's extra tick was hiding. Babel-layer root fix in [facebook/react-native#56816](https://github.com/facebook/react-native/pull/56816). | [get-convex/better-auth#368](https://github.com/get-convex/better-auth/pull/368) | | [`better-auth`](packages/better-auth/) | `1.6.11` | Preserve the caller's session on `/change-password` with `revokeOtherSessions: true`. Same family as [#9087](https://github.com/better-auth/better-auth/pull/9087). | [better-auth/better-auth#9345](https://github.com/better-auth/better-auth/pull/9345) | | [`@hugeicons/react`](packages/@hugeicons/react/) | `1.1.6` | Ship subpath types for `@hugeicons/core-free-icons/*` so TS finds them under `node16`, `nodenext`, and `bundler` resolution. Vite dev stops pre-bundling the 6.2 MB barrel for the 33 KB you actually use. | [hugeicons/react#5](https://github.com/hugeicons/react/pull/5) | diff --git a/packages/@react-native/babel-preset/bun/@react-native%2Fbabel-preset@0.85.3-pr56816.patch b/packages/@react-native/babel-preset/bun/@react-native%2Fbabel-preset@0.85.3-pr56816.patch deleted file mode 100644 index 7e1d007..0000000 --- a/packages/@react-native/babel-preset/bun/@react-native%2Fbabel-preset@0.85.3-pr56816.patch +++ /dev/null @@ -1,722 +0,0 @@ -diff --git a/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js b/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -new file mode 100644 -index 000000000000..1365556a96d9 ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -@@ -0,0 +1,115 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1AsyncArrowNonSimpleParams = require('../fix-hermes-v1-async-arrow-non-simple-params'); -+ -+test('rewrites destructured object param with default to simple identifier', () => { -+ const code = ` -+ const fn = async ({a = 1, b} = {}) => { -+ return await fetch(a + b); -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var { -+ a = 1, -+ b -+ } = _p === undefined ? {} : _p; -+ return await fetch(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites destructured array param to simple identifier', () => { -+ const code = ` -+ const fn = async ([a, b]) => await Promise.resolve(a + b); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var [a, b] = _p; -+ return await Promise.resolve(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites assignment-pattern param without enclosing destructure', () => { -+ const code = ` -+ const fn = async (x = 5) => await use(x); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var x = _p === undefined ? 5 : _p; -+ return await use(x); -+ };" -+ `); -+}); -+ -+test('wraps body in inner async arrow when rest param is present', () => { -+ const code = ` -+ const fn = async (...args) => await handle(args); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = (...args) => (async () => { -+ return await handle(args); -+ })();" -+ `); -+}); -+ -+test('leaves async arrow with only simple identifier params alone', () => { -+ const code = ` -+ const fn = async (a, b) => await fetch(a + b); -+ `; -+ -+ expect( -+ transform(code, [fixHermesV1AsyncArrowNonSimpleParams]), -+ ).toMatchInlineSnapshot(`"const fn = async (a, b) => await fetch(a + b);"`); -+}); -+ -+test('leaves non-async arrow alone', () => { -+ const code = ` -+ const fn = ({a = 1, b} = {}) => a + b; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = ({ -+ a = 1, -+ b -+ } = {}) => a + b;" -+ `); -+}); -+ -+test('handles multiple params mixing simple and complex', () => { -+ const code = ` -+ const fn = async (a, {b}, c = 1) => await all(a, b, c); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async (a, _p, _p2) => { -+ var { -+ b -+ } = _p; -+ var c = _p2 === undefined ? 1 : _p2; -+ return await all(a, b, c); -+ };" -+ `); -+}); -diff --git a/src/__tests__/fix-hermes-v1-class-in-finally-test.js b/src/__tests__/fix-hermes-v1-class-in-finally-test.js -new file mode 100644 -index 000000000000..d3f6a6aec1ab ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-class-in-finally-test.js -@@ -0,0 +1,135 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1ClassInFinally = require('../fix-hermes-v1-class-in-finally'); -+ -+test('wraps class declaration in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ class Logger { -+ log() { console.log('done'); } -+ } -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ var Logger = (() => { -+ class Logger { -+ log() { -+ console.log('done'); -+ } -+ } -+ return Logger; -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('wraps class expression in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = class { -+ log() {} -+ }; -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = (() => class { -+ log() {} -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class outside finally block alone', () => { -+ const code = ` -+ function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class declared at module scope alone', () => { -+ const code = ` -+ class Module {} -+ new Module(); -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "class Module {} -+ new Module();" -+ `); -+}); -+ -+test('does not enter nested function scope', () => { -+ const code = ` -+ try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ }" -+ `); -+}); -diff --git a/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js b/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -new file mode 100644 -index 000000000000..1512c8b91e99 ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -@@ -0,0 +1,128 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1SuperInObjectAccessor = require('../fix-hermes-v1-super-in-object-accessor'); -+ -+test('rewrites identifier-keyed object getter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ get name() { -+ return super.name; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [\\"name\\"]() { -+ return super.name; -+ } -+ };" -+ `); -+}); -+ -+test('rewrites identifier-keyed object setter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ set value(v) { -+ super.value = v; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ set [\\"value\\"](v) { -+ super.value = v; -+ } -+ };" -+ `); -+}); -+ -+test('leaves super inside class method alone', () => { -+ const code = ` -+ class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ }" -+ `); -+}); -+ -+test('leaves super inside regular object method alone', () => { -+ const code = ` -+ const obj = { -+ run() { -+ return super.run(); -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ run() { -+ return super.run(); -+ } -+ };" -+ `); -+}); -+ -+test('leaves super() call alone', () => { -+ const code = ` -+ class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ }" -+ `); -+}); -+ -+test('skips already-computed accessor', () => { -+ const code = ` -+ const obj = { -+ get [keyName]() { -+ return super.value; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [keyName]() { -+ return super.value; -+ } -+ };" -+ `); -+}); -diff --git a/src/configs/main.js b/src/configs/main.js -index 5ceeced5f42d..fca192d519b5 100644 ---- a/src/configs/main.js -+++ b/src/configs/main.js -@@ -231,6 +231,21 @@ const getPreset = (src, options, babel) => { - ...options.hermesParserOptions, - }, - ], -+ // Hermes V1 native runtime workarounds. See each plugin file header -+ // for the matching facebook/hermes commit and the bug it patches. -+ ...(isHermesProfile -+ ? [ -+ [require('../fix-hermes-v1-class-in-finally')], -+ [require('../fix-hermes-v1-super-in-object-accessor')], -+ ...(preserveAsync -+ ? [ -+ [ -+ require('../fix-hermes-v1-async-arrow-non-simple-params'), -+ ], -+ ] -+ : []), -+ ] -+ : []), - [require('babel-plugin-transform-flow-enums')], - ...(preserveBlockScoping - ? [] -diff --git a/src/fix-hermes-v1-async-arrow-non-simple-params.js b/src/fix-hermes-v1-async-arrow-non-simple-params.js -new file mode 100644 -index 000000000000..9710c60e4d97 ---- /dev/null -+++ b/src/fix-hermes-v1-async-arrow-non-simple-params.js -@@ -0,0 +1,93 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for https://github.com/facebook/hermes/issues/1761. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/68bfb3a48b31 -+// (2025-09-11) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Async arrow functions with non-simple parameters (destructured patterns, -+// defaults, rest) cause Hermes V1 to resolve `await` with `undefined` while -+// the function body continues executing in the background. This rewrites the -+// arrow into one with a simple identifier parameter and inline destructuring -+// so Hermes never sees the buggy shape. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-async-arrow-non-simple-params', -+ visitor: { -+ ArrowFunctionExpression(path) { -+ const {node} = path; -+ if (!node.async || node.params.every(p => t.isIdentifier(p))) { -+ return; -+ } -+ -+ // Hermes V1 rejects any rest param on async arrows. Wrap the body in -+ // a sync arrow that calls an inner async arrow with no params. -+ if (node.params.some(p => t.isRestElement(p))) { -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ const innerAsync = t.arrowFunctionExpression([], body, true); -+ node.async = false; -+ node.body = t.callExpression(innerAsync, []); -+ return; -+ } -+ -+ const newParams = []; -+ const init = []; -+ for (const param of node.params) { -+ if (t.isIdentifier(param)) { -+ newParams.push(param); -+ continue; -+ } -+ -+ const sym = path.scope.generateUidIdentifier('p'); -+ if (t.isAssignmentPattern(param)) { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator( -+ param.left, -+ t.conditionalExpression( -+ t.binaryExpression( -+ '===', -+ t.cloneNode(sym), -+ t.identifier('undefined'), -+ ), -+ param.right, -+ t.cloneNode(sym), -+ ), -+ ), -+ ]), -+ ); -+ } else { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(param, t.cloneNode(sym)), -+ ]), -+ ); -+ } -+ } -+ -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ body.body.unshift(...init); -+ node.params = newParams; -+ node.body = body; -+ }, -+ }, -+}); -diff --git a/src/fix-hermes-v1-class-in-finally.js b/src/fix-hermes-v1-class-in-finally.js -new file mode 100644 -index 000000000000..289aec9224ac ---- /dev/null -+++ b/src/fix-hermes-v1-class-in-finally.js -@@ -0,0 +1,97 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the variable-caching-for-legacy-classes bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/1e94fbe0ebb4 -+// (2026-02-12) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Class declarations inside a `finally` block trip Hermes V1's variable -+// caching path. Wrap them in an IIFE so the class lives in its own function -+// scope and the cache miss never happens. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function isInFinalizerScope(path) { -+ let inner = path; -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const type = parentPath.node.type; -+ switch (type) { -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'ArrowFunctionExpression': -+ case 'ObjectMethod': -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'StaticBlock': -+ return false; -+ case 'TryStatement': -+ if (inner.key === 'finalizer') { -+ return true; -+ } -+ break; -+ } -+ inner = parentPath; -+ parentPath = parentPath.parentPath; -+ } -+ return false; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-class-in-finally', -+ visitor: { -+ ClassDeclaration(path) { -+ const id = path.node.id; -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !id || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const inner = t.classDeclaration( -+ t.cloneNode(id), -+ path.node.superClass, -+ path.node.body, -+ [], -+ ); -+ -+ const arrow = t.arrowFunctionExpression( -+ [], -+ t.blockStatement([inner, t.returnStatement(t.cloneNode(id))]), -+ ); -+ -+ path.replaceWith( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(t.cloneNode(id), t.callExpression(arrow, [])), -+ ]), -+ ); -+ path.skip(); -+ }, -+ -+ ClassExpression(path) { -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const arrow = t.arrowFunctionExpression([], path.node); -+ path.replaceWith(t.callExpression(arrow, [])); -+ path.skip(); -+ }, -+ }, -+}); -diff --git a/src/fix-hermes-v1-super-in-object-accessor.js b/src/fix-hermes-v1-super-in-object-accessor.js -new file mode 100644 -index 000000000000..4d793ac6729d ---- /dev/null -+++ b/src/fix-hermes-v1-super-in-object-accessor.js -@@ -0,0 +1,74 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the genFunctionExpression home-object bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/18a963465944 -+// (2025-11-04) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Object-literal getters and setters that use `super.x` lookups trip Hermes -+// V1's home-object path. Rewriting the accessor with a computed string key -+// avoids the buggy codegen. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function findEnclosingNonComputedObjectAccessor(path) { -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const node = parentPath.node; -+ const type = node.type; -+ switch (type) { -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'StaticBlock': -+ case 'ClassProperty': -+ case 'ClassPrivateProperty': -+ return null; -+ case 'ObjectMethod': -+ if (!node.computed && (node.kind === 'get' || node.kind === 'set')) { -+ return node; -+ } -+ return null; -+ } -+ parentPath = parentPath.parentPath; -+ } -+ return null; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-super-in-object-accessor', -+ visitor: { -+ Super(path) { -+ // Only `super.x` / `super[expr]` reach the buggy home-object path. -+ // `super()` lives only in derived class constructors and takes a -+ // different codepath. -+ const parent = path.parent; -+ if (parent.type !== 'MemberExpression' || parent.object !== path.node) { -+ return; -+ } -+ -+ const accessor = findEnclosingNonComputedObjectAccessor(path); -+ if (accessor) { -+ const key = accessor.key; -+ if (key.type === 'Identifier') { -+ accessor.key = t.stringLiteral(key.name); -+ } else if (key.type !== 'StringLiteral') { -+ return; -+ } -+ accessor.computed = true; -+ } -+ }, -+ }, -+}); -diff --git a/src/index.js b/src/index.js -index e785c9759386..cd3575d6e755 100644 ---- a/src/index.js -+++ b/src/index.js -@@ -41,6 +41,13 @@ module.exports.getCacheKey = () => { - readFileSync(require.resolve('./configs/lazy-imports.js')), - readFileSync(require.resolve('./passthrough-syntax-plugins.js')), - readFileSync(require.resolve('./plugin-warn-on-deep-imports.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-async-arrow-non-simple-params.js'), -+ ), -+ readFileSync(require.resolve('./fix-hermes-v1-class-in-finally.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-super-in-object-accessor.js'), -+ ), - ].forEach(part => key.update(part)); - cacheKey = key.digest('hex'); - return cacheKey; diff --git a/packages/@react-native/babel-preset/npm/@react-native+babel-preset+0.85.3-pr56816.patch b/packages/@react-native/babel-preset/npm/@react-native+babel-preset+0.85.3-pr56816.patch deleted file mode 100644 index a6b949a..0000000 --- a/packages/@react-native/babel-preset/npm/@react-native+babel-preset+0.85.3-pr56816.patch +++ /dev/null @@ -1,722 +0,0 @@ -diff --git a/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -new file mode 100644 -index 000000000000..1365556a96d9 ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -@@ -0,0 +1,115 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1AsyncArrowNonSimpleParams = require('../fix-hermes-v1-async-arrow-non-simple-params'); -+ -+test('rewrites destructured object param with default to simple identifier', () => { -+ const code = ` -+ const fn = async ({a = 1, b} = {}) => { -+ return await fetch(a + b); -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var { -+ a = 1, -+ b -+ } = _p === undefined ? {} : _p; -+ return await fetch(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites destructured array param to simple identifier', () => { -+ const code = ` -+ const fn = async ([a, b]) => await Promise.resolve(a + b); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var [a, b] = _p; -+ return await Promise.resolve(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites assignment-pattern param without enclosing destructure', () => { -+ const code = ` -+ const fn = async (x = 5) => await use(x); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var x = _p === undefined ? 5 : _p; -+ return await use(x); -+ };" -+ `); -+}); -+ -+test('wraps body in inner async arrow when rest param is present', () => { -+ const code = ` -+ const fn = async (...args) => await handle(args); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = (...args) => (async () => { -+ return await handle(args); -+ })();" -+ `); -+}); -+ -+test('leaves async arrow with only simple identifier params alone', () => { -+ const code = ` -+ const fn = async (a, b) => await fetch(a + b); -+ `; -+ -+ expect( -+ transform(code, [fixHermesV1AsyncArrowNonSimpleParams]), -+ ).toMatchInlineSnapshot(`"const fn = async (a, b) => await fetch(a + b);"`); -+}); -+ -+test('leaves non-async arrow alone', () => { -+ const code = ` -+ const fn = ({a = 1, b} = {}) => a + b; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = ({ -+ a = 1, -+ b -+ } = {}) => a + b;" -+ `); -+}); -+ -+test('handles multiple params mixing simple and complex', () => { -+ const code = ` -+ const fn = async (a, {b}, c = 1) => await all(a, b, c); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async (a, _p, _p2) => { -+ var { -+ b -+ } = _p; -+ var c = _p2 === undefined ? 1 : _p2; -+ return await all(a, b, c); -+ };" -+ `); -+}); -diff --git a/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-class-in-finally-test.js b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-class-in-finally-test.js -new file mode 100644 -index 000000000000..d3f6a6aec1ab ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-class-in-finally-test.js -@@ -0,0 +1,135 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1ClassInFinally = require('../fix-hermes-v1-class-in-finally'); -+ -+test('wraps class declaration in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ class Logger { -+ log() { console.log('done'); } -+ } -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ var Logger = (() => { -+ class Logger { -+ log() { -+ console.log('done'); -+ } -+ } -+ return Logger; -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('wraps class expression in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = class { -+ log() {} -+ }; -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = (() => class { -+ log() {} -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class outside finally block alone', () => { -+ const code = ` -+ function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class declared at module scope alone', () => { -+ const code = ` -+ class Module {} -+ new Module(); -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "class Module {} -+ new Module();" -+ `); -+}); -+ -+test('does not enter nested function scope', () => { -+ const code = ` -+ try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ }" -+ `); -+}); -diff --git a/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -new file mode 100644 -index 000000000000..1512c8b91e99 ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -@@ -0,0 +1,128 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1SuperInObjectAccessor = require('../fix-hermes-v1-super-in-object-accessor'); -+ -+test('rewrites identifier-keyed object getter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ get name() { -+ return super.name; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [\\"name\\"]() { -+ return super.name; -+ } -+ };" -+ `); -+}); -+ -+test('rewrites identifier-keyed object setter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ set value(v) { -+ super.value = v; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ set [\\"value\\"](v) { -+ super.value = v; -+ } -+ };" -+ `); -+}); -+ -+test('leaves super inside class method alone', () => { -+ const code = ` -+ class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ }" -+ `); -+}); -+ -+test('leaves super inside regular object method alone', () => { -+ const code = ` -+ const obj = { -+ run() { -+ return super.run(); -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ run() { -+ return super.run(); -+ } -+ };" -+ `); -+}); -+ -+test('leaves super() call alone', () => { -+ const code = ` -+ class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ }" -+ `); -+}); -+ -+test('skips already-computed accessor', () => { -+ const code = ` -+ const obj = { -+ get [keyName]() { -+ return super.value; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [keyName]() { -+ return super.value; -+ } -+ };" -+ `); -+}); -diff --git a/node_modules/@react-native/babel-preset/src/configs/main.js b/node_modules/@react-native/babel-preset/src/configs/main.js -index 5ceeced5f42d..fca192d519b5 100644 ---- a/node_modules/@react-native/babel-preset/src/configs/main.js -+++ b/node_modules/@react-native/babel-preset/src/configs/main.js -@@ -231,6 +231,21 @@ const getPreset = (src, options, babel) => { - ...options.hermesParserOptions, - }, - ], -+ // Hermes V1 native runtime workarounds. See each plugin file header -+ // for the matching facebook/hermes commit and the bug it patches. -+ ...(isHermesProfile -+ ? [ -+ [require('../fix-hermes-v1-class-in-finally')], -+ [require('../fix-hermes-v1-super-in-object-accessor')], -+ ...(preserveAsync -+ ? [ -+ [ -+ require('../fix-hermes-v1-async-arrow-non-simple-params'), -+ ], -+ ] -+ : []), -+ ] -+ : []), - [require('babel-plugin-transform-flow-enums')], - ...(preserveBlockScoping - ? [] -diff --git a/node_modules/@react-native/babel-preset/src/fix-hermes-v1-async-arrow-non-simple-params.js b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-async-arrow-non-simple-params.js -new file mode 100644 -index 000000000000..9710c60e4d97 ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-async-arrow-non-simple-params.js -@@ -0,0 +1,93 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for https://github.com/facebook/hermes/issues/1761. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/68bfb3a48b31 -+// (2025-09-11) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Async arrow functions with non-simple parameters (destructured patterns, -+// defaults, rest) cause Hermes V1 to resolve `await` with `undefined` while -+// the function body continues executing in the background. This rewrites the -+// arrow into one with a simple identifier parameter and inline destructuring -+// so Hermes never sees the buggy shape. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-async-arrow-non-simple-params', -+ visitor: { -+ ArrowFunctionExpression(path) { -+ const {node} = path; -+ if (!node.async || node.params.every(p => t.isIdentifier(p))) { -+ return; -+ } -+ -+ // Hermes V1 rejects any rest param on async arrows. Wrap the body in -+ // a sync arrow that calls an inner async arrow with no params. -+ if (node.params.some(p => t.isRestElement(p))) { -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ const innerAsync = t.arrowFunctionExpression([], body, true); -+ node.async = false; -+ node.body = t.callExpression(innerAsync, []); -+ return; -+ } -+ -+ const newParams = []; -+ const init = []; -+ for (const param of node.params) { -+ if (t.isIdentifier(param)) { -+ newParams.push(param); -+ continue; -+ } -+ -+ const sym = path.scope.generateUidIdentifier('p'); -+ if (t.isAssignmentPattern(param)) { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator( -+ param.left, -+ t.conditionalExpression( -+ t.binaryExpression( -+ '===', -+ t.cloneNode(sym), -+ t.identifier('undefined'), -+ ), -+ param.right, -+ t.cloneNode(sym), -+ ), -+ ), -+ ]), -+ ); -+ } else { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(param, t.cloneNode(sym)), -+ ]), -+ ); -+ } -+ } -+ -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ body.body.unshift(...init); -+ node.params = newParams; -+ node.body = body; -+ }, -+ }, -+}); -diff --git a/node_modules/@react-native/babel-preset/src/fix-hermes-v1-class-in-finally.js b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-class-in-finally.js -new file mode 100644 -index 000000000000..289aec9224ac ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-class-in-finally.js -@@ -0,0 +1,97 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the variable-caching-for-legacy-classes bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/1e94fbe0ebb4 -+// (2026-02-12) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Class declarations inside a `finally` block trip Hermes V1's variable -+// caching path. Wrap them in an IIFE so the class lives in its own function -+// scope and the cache miss never happens. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function isInFinalizerScope(path) { -+ let inner = path; -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const type = parentPath.node.type; -+ switch (type) { -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'ArrowFunctionExpression': -+ case 'ObjectMethod': -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'StaticBlock': -+ return false; -+ case 'TryStatement': -+ if (inner.key === 'finalizer') { -+ return true; -+ } -+ break; -+ } -+ inner = parentPath; -+ parentPath = parentPath.parentPath; -+ } -+ return false; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-class-in-finally', -+ visitor: { -+ ClassDeclaration(path) { -+ const id = path.node.id; -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !id || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const inner = t.classDeclaration( -+ t.cloneNode(id), -+ path.node.superClass, -+ path.node.body, -+ [], -+ ); -+ -+ const arrow = t.arrowFunctionExpression( -+ [], -+ t.blockStatement([inner, t.returnStatement(t.cloneNode(id))]), -+ ); -+ -+ path.replaceWith( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(t.cloneNode(id), t.callExpression(arrow, [])), -+ ]), -+ ); -+ path.skip(); -+ }, -+ -+ ClassExpression(path) { -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const arrow = t.arrowFunctionExpression([], path.node); -+ path.replaceWith(t.callExpression(arrow, [])); -+ path.skip(); -+ }, -+ }, -+}); -diff --git a/node_modules/@react-native/babel-preset/src/fix-hermes-v1-super-in-object-accessor.js b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-super-in-object-accessor.js -new file mode 100644 -index 000000000000..4d793ac6729d ---- /dev/null -+++ b/node_modules/@react-native/babel-preset/src/fix-hermes-v1-super-in-object-accessor.js -@@ -0,0 +1,74 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the genFunctionExpression home-object bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/18a963465944 -+// (2025-11-04) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Object-literal getters and setters that use `super.x` lookups trip Hermes -+// V1's home-object path. Rewriting the accessor with a computed string key -+// avoids the buggy codegen. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function findEnclosingNonComputedObjectAccessor(path) { -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const node = parentPath.node; -+ const type = node.type; -+ switch (type) { -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'StaticBlock': -+ case 'ClassProperty': -+ case 'ClassPrivateProperty': -+ return null; -+ case 'ObjectMethod': -+ if (!node.computed && (node.kind === 'get' || node.kind === 'set')) { -+ return node; -+ } -+ return null; -+ } -+ parentPath = parentPath.parentPath; -+ } -+ return null; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-super-in-object-accessor', -+ visitor: { -+ Super(path) { -+ // Only `super.x` / `super[expr]` reach the buggy home-object path. -+ // `super()` lives only in derived class constructors and takes a -+ // different codepath. -+ const parent = path.parent; -+ if (parent.type !== 'MemberExpression' || parent.object !== path.node) { -+ return; -+ } -+ -+ const accessor = findEnclosingNonComputedObjectAccessor(path); -+ if (accessor) { -+ const key = accessor.key; -+ if (key.type === 'Identifier') { -+ accessor.key = t.stringLiteral(key.name); -+ } else if (key.type !== 'StringLiteral') { -+ return; -+ } -+ accessor.computed = true; -+ } -+ }, -+ }, -+}); -diff --git a/node_modules/@react-native/babel-preset/src/index.js b/node_modules/@react-native/babel-preset/src/index.js -index e785c9759386..cd3575d6e755 100644 ---- a/node_modules/@react-native/babel-preset/src/index.js -+++ b/node_modules/@react-native/babel-preset/src/index.js -@@ -41,6 +41,13 @@ module.exports.getCacheKey = () => { - readFileSync(require.resolve('./configs/lazy-imports.js')), - readFileSync(require.resolve('./passthrough-syntax-plugins.js')), - readFileSync(require.resolve('./plugin-warn-on-deep-imports.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-async-arrow-non-simple-params.js'), -+ ), -+ readFileSync(require.resolve('./fix-hermes-v1-class-in-finally.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-super-in-object-accessor.js'), -+ ), - ].forEach(part => key.update(part)); - cacheKey = key.digest('hex'); - return cacheKey; diff --git a/packages/@react-native/babel-preset/pnpm/@react-native__babel-preset@0.85.3-pr56816.patch b/packages/@react-native/babel-preset/pnpm/@react-native__babel-preset@0.85.3-pr56816.patch deleted file mode 100644 index 7e1d007..0000000 --- a/packages/@react-native/babel-preset/pnpm/@react-native__babel-preset@0.85.3-pr56816.patch +++ /dev/null @@ -1,722 +0,0 @@ -diff --git a/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js b/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -new file mode 100644 -index 000000000000..1365556a96d9 ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-async-arrow-non-simple-params-test.js -@@ -0,0 +1,115 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1AsyncArrowNonSimpleParams = require('../fix-hermes-v1-async-arrow-non-simple-params'); -+ -+test('rewrites destructured object param with default to simple identifier', () => { -+ const code = ` -+ const fn = async ({a = 1, b} = {}) => { -+ return await fetch(a + b); -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var { -+ a = 1, -+ b -+ } = _p === undefined ? {} : _p; -+ return await fetch(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites destructured array param to simple identifier', () => { -+ const code = ` -+ const fn = async ([a, b]) => await Promise.resolve(a + b); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var [a, b] = _p; -+ return await Promise.resolve(a + b); -+ };" -+ `); -+}); -+ -+test('rewrites assignment-pattern param without enclosing destructure', () => { -+ const code = ` -+ const fn = async (x = 5) => await use(x); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async _p => { -+ var x = _p === undefined ? 5 : _p; -+ return await use(x); -+ };" -+ `); -+}); -+ -+test('wraps body in inner async arrow when rest param is present', () => { -+ const code = ` -+ const fn = async (...args) => await handle(args); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = (...args) => (async () => { -+ return await handle(args); -+ })();" -+ `); -+}); -+ -+test('leaves async arrow with only simple identifier params alone', () => { -+ const code = ` -+ const fn = async (a, b) => await fetch(a + b); -+ `; -+ -+ expect( -+ transform(code, [fixHermesV1AsyncArrowNonSimpleParams]), -+ ).toMatchInlineSnapshot(`"const fn = async (a, b) => await fetch(a + b);"`); -+}); -+ -+test('leaves non-async arrow alone', () => { -+ const code = ` -+ const fn = ({a = 1, b} = {}) => a + b; -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = ({ -+ a = 1, -+ b -+ } = {}) => a + b;" -+ `); -+}); -+ -+test('handles multiple params mixing simple and complex', () => { -+ const code = ` -+ const fn = async (a, {b}, c = 1) => await all(a, b, c); -+ `; -+ -+ expect(transform(code, [fixHermesV1AsyncArrowNonSimpleParams])) -+ .toMatchInlineSnapshot(` -+ "const fn = async (a, _p, _p2) => { -+ var { -+ b -+ } = _p; -+ var c = _p2 === undefined ? 1 : _p2; -+ return await all(a, b, c); -+ };" -+ `); -+}); -diff --git a/src/__tests__/fix-hermes-v1-class-in-finally-test.js b/src/__tests__/fix-hermes-v1-class-in-finally-test.js -new file mode 100644 -index 000000000000..d3f6a6aec1ab ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-class-in-finally-test.js -@@ -0,0 +1,135 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1ClassInFinally = require('../fix-hermes-v1-class-in-finally'); -+ -+test('wraps class declaration in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ class Logger { -+ log() { console.log('done'); } -+ } -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ var Logger = (() => { -+ class Logger { -+ log() { -+ console.log('done'); -+ } -+ } -+ return Logger; -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('wraps class expression in finally block in IIFE', () => { -+ const code = ` -+ function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = class { -+ log() {} -+ }; -+ new Logger().log(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ risky(); -+ } finally { -+ const Logger = (() => class { -+ log() {} -+ })(); -+ new Logger().log(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class outside finally block alone', () => { -+ const code = ` -+ function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "function run() { -+ try { -+ class Inside {} -+ return new Inside(); -+ } catch (e) { -+ class Caught {} -+ return new Caught(); -+ } -+ }" -+ `); -+}); -+ -+test('leaves class declared at module scope alone', () => { -+ const code = ` -+ class Module {} -+ new Module(); -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "class Module {} -+ new Module();" -+ `); -+}); -+ -+test('does not enter nested function scope', () => { -+ const code = ` -+ try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1ClassInFinally])).toMatchInlineSnapshot(` -+ "try {} finally { -+ function inner() { -+ class NestedFn {} -+ return new NestedFn(); -+ } -+ inner(); -+ }" -+ `); -+}); -diff --git a/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js b/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -new file mode 100644 -index 000000000000..1512c8b91e99 ---- /dev/null -+++ b/src/__tests__/fix-hermes-v1-super-in-object-accessor-test.js -@@ -0,0 +1,128 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+const {transform} = require('../__mocks__/test-helpers'); -+const fixHermesV1SuperInObjectAccessor = require('../fix-hermes-v1-super-in-object-accessor'); -+ -+test('rewrites identifier-keyed object getter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ get name() { -+ return super.name; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [\\"name\\"]() { -+ return super.name; -+ } -+ };" -+ `); -+}); -+ -+test('rewrites identifier-keyed object setter using super.x to computed string key', () => { -+ const code = ` -+ const obj = { -+ set value(v) { -+ super.value = v; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ set [\\"value\\"](v) { -+ super.value = v; -+ } -+ };" -+ `); -+}); -+ -+test('leaves super inside class method alone', () => { -+ const code = ` -+ class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ get name() { -+ return super.name; -+ } -+ }" -+ `); -+}); -+ -+test('leaves super inside regular object method alone', () => { -+ const code = ` -+ const obj = { -+ run() { -+ return super.run(); -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ run() { -+ return super.run(); -+ } -+ };" -+ `); -+}); -+ -+test('leaves super() call alone', () => { -+ const code = ` -+ class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ } -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "class Child extends Parent { -+ constructor() { -+ super(); -+ } -+ }" -+ `); -+}); -+ -+test('skips already-computed accessor', () => { -+ const code = ` -+ const obj = { -+ get [keyName]() { -+ return super.value; -+ }, -+ }; -+ `; -+ -+ expect(transform(code, [fixHermesV1SuperInObjectAccessor])) -+ .toMatchInlineSnapshot(` -+ "const obj = { -+ get [keyName]() { -+ return super.value; -+ } -+ };" -+ `); -+}); -diff --git a/src/configs/main.js b/src/configs/main.js -index 5ceeced5f42d..fca192d519b5 100644 ---- a/src/configs/main.js -+++ b/src/configs/main.js -@@ -231,6 +231,21 @@ const getPreset = (src, options, babel) => { - ...options.hermesParserOptions, - }, - ], -+ // Hermes V1 native runtime workarounds. See each plugin file header -+ // for the matching facebook/hermes commit and the bug it patches. -+ ...(isHermesProfile -+ ? [ -+ [require('../fix-hermes-v1-class-in-finally')], -+ [require('../fix-hermes-v1-super-in-object-accessor')], -+ ...(preserveAsync -+ ? [ -+ [ -+ require('../fix-hermes-v1-async-arrow-non-simple-params'), -+ ], -+ ] -+ : []), -+ ] -+ : []), - [require('babel-plugin-transform-flow-enums')], - ...(preserveBlockScoping - ? [] -diff --git a/src/fix-hermes-v1-async-arrow-non-simple-params.js b/src/fix-hermes-v1-async-arrow-non-simple-params.js -new file mode 100644 -index 000000000000..9710c60e4d97 ---- /dev/null -+++ b/src/fix-hermes-v1-async-arrow-non-simple-params.js -@@ -0,0 +1,93 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for https://github.com/facebook/hermes/issues/1761. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/68bfb3a48b31 -+// (2025-09-11) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Async arrow functions with non-simple parameters (destructured patterns, -+// defaults, rest) cause Hermes V1 to resolve `await` with `undefined` while -+// the function body continues executing in the background. This rewrites the -+// arrow into one with a simple identifier parameter and inline destructuring -+// so Hermes never sees the buggy shape. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-async-arrow-non-simple-params', -+ visitor: { -+ ArrowFunctionExpression(path) { -+ const {node} = path; -+ if (!node.async || node.params.every(p => t.isIdentifier(p))) { -+ return; -+ } -+ -+ // Hermes V1 rejects any rest param on async arrows. Wrap the body in -+ // a sync arrow that calls an inner async arrow with no params. -+ if (node.params.some(p => t.isRestElement(p))) { -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ const innerAsync = t.arrowFunctionExpression([], body, true); -+ node.async = false; -+ node.body = t.callExpression(innerAsync, []); -+ return; -+ } -+ -+ const newParams = []; -+ const init = []; -+ for (const param of node.params) { -+ if (t.isIdentifier(param)) { -+ newParams.push(param); -+ continue; -+ } -+ -+ const sym = path.scope.generateUidIdentifier('p'); -+ if (t.isAssignmentPattern(param)) { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator( -+ param.left, -+ t.conditionalExpression( -+ t.binaryExpression( -+ '===', -+ t.cloneNode(sym), -+ t.identifier('undefined'), -+ ), -+ param.right, -+ t.cloneNode(sym), -+ ), -+ ), -+ ]), -+ ); -+ } else { -+ newParams.push(sym); -+ init.push( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(param, t.cloneNode(sym)), -+ ]), -+ ); -+ } -+ } -+ -+ const body = !t.isBlockStatement(node.body) -+ ? t.blockStatement([t.returnStatement(node.body)]) -+ : node.body; -+ body.body.unshift(...init); -+ node.params = newParams; -+ node.body = body; -+ }, -+ }, -+}); -diff --git a/src/fix-hermes-v1-class-in-finally.js b/src/fix-hermes-v1-class-in-finally.js -new file mode 100644 -index 000000000000..289aec9224ac ---- /dev/null -+++ b/src/fix-hermes-v1-class-in-finally.js -@@ -0,0 +1,97 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the variable-caching-for-legacy-classes bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/1e94fbe0ebb4 -+// (2026-02-12) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Class declarations inside a `finally` block trip Hermes V1's variable -+// caching path. Wrap them in an IIFE so the class lives in its own function -+// scope and the cache miss never happens. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function isInFinalizerScope(path) { -+ let inner = path; -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const type = parentPath.node.type; -+ switch (type) { -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'ArrowFunctionExpression': -+ case 'ObjectMethod': -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'StaticBlock': -+ return false; -+ case 'TryStatement': -+ if (inner.key === 'finalizer') { -+ return true; -+ } -+ break; -+ } -+ inner = parentPath; -+ parentPath = parentPath.parentPath; -+ } -+ return false; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-class-in-finally', -+ visitor: { -+ ClassDeclaration(path) { -+ const id = path.node.id; -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !id || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const inner = t.classDeclaration( -+ t.cloneNode(id), -+ path.node.superClass, -+ path.node.body, -+ [], -+ ); -+ -+ const arrow = t.arrowFunctionExpression( -+ [], -+ t.blockStatement([inner, t.returnStatement(t.cloneNode(id))]), -+ ); -+ -+ path.replaceWith( -+ t.variableDeclaration('var', [ -+ t.variableDeclarator(t.cloneNode(id), t.callExpression(arrow, [])), -+ ]), -+ ); -+ path.skip(); -+ }, -+ -+ ClassExpression(path) { -+ if ( -+ (path.node.decorators && path.node.decorators.length) || -+ !isInFinalizerScope(path) -+ ) { -+ return; -+ } -+ -+ const arrow = t.arrowFunctionExpression([], path.node); -+ path.replaceWith(t.callExpression(arrow, [])); -+ path.skip(); -+ }, -+ }, -+}); -diff --git a/src/fix-hermes-v1-super-in-object-accessor.js b/src/fix-hermes-v1-super-in-object-accessor.js -new file mode 100644 -index 000000000000..4d793ac6729d ---- /dev/null -+++ b/src/fix-hermes-v1-super-in-object-accessor.js -@@ -0,0 +1,74 @@ -+/** -+ * Copyright (c) Meta Platforms, Inc. and affiliates. -+ * -+ * This source code is licensed under the MIT license found in the -+ * LICENSE file in the root directory of this source tree. -+ * -+ * @format -+ * @noflow -+ */ -+ -+'use strict'; -+ -+// Workaround for the genFunctionExpression home-object bug in Hermes V1. -+// Fixed in Hermes mainline by https://github.com/facebook/hermes/commit/18a963465944 -+// (2025-11-04) but the bundled Hermes V1 in this version of React Native -+// (250829098.0.13, 2025-08-29 branch cut) predates the fix. -+// -+// Object-literal getters and setters that use `super.x` lookups trip Hermes -+// V1's home-object path. Rewriting the accessor with a computed string key -+// avoids the buggy codegen. -+// -+// Ported from `babel-preset-expo` (https://github.com/expo/expo/pull/45601), -+// MIT licensed. -+ -+function findEnclosingNonComputedObjectAccessor(path) { -+ let parentPath = path.parentPath; -+ while (parentPath) { -+ const node = parentPath.node; -+ const type = node.type; -+ switch (type) { -+ case 'ClassMethod': -+ case 'ClassPrivateMethod': -+ case 'FunctionExpression': -+ case 'FunctionDeclaration': -+ case 'StaticBlock': -+ case 'ClassProperty': -+ case 'ClassPrivateProperty': -+ return null; -+ case 'ObjectMethod': -+ if (!node.computed && (node.kind === 'get' || node.kind === 'set')) { -+ return node; -+ } -+ return null; -+ } -+ parentPath = parentPath.parentPath; -+ } -+ return null; -+} -+ -+module.exports = ({types: t}) => ({ -+ name: 'fix-hermes-v1-super-in-object-accessor', -+ visitor: { -+ Super(path) { -+ // Only `super.x` / `super[expr]` reach the buggy home-object path. -+ // `super()` lives only in derived class constructors and takes a -+ // different codepath. -+ const parent = path.parent; -+ if (parent.type !== 'MemberExpression' || parent.object !== path.node) { -+ return; -+ } -+ -+ const accessor = findEnclosingNonComputedObjectAccessor(path); -+ if (accessor) { -+ const key = accessor.key; -+ if (key.type === 'Identifier') { -+ accessor.key = t.stringLiteral(key.name); -+ } else if (key.type !== 'StringLiteral') { -+ return; -+ } -+ accessor.computed = true; -+ } -+ }, -+ }, -+}); -diff --git a/src/index.js b/src/index.js -index e785c9759386..cd3575d6e755 100644 ---- a/src/index.js -+++ b/src/index.js -@@ -41,6 +41,13 @@ module.exports.getCacheKey = () => { - readFileSync(require.resolve('./configs/lazy-imports.js')), - readFileSync(require.resolve('./passthrough-syntax-plugins.js')), - readFileSync(require.resolve('./plugin-warn-on-deep-imports.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-async-arrow-non-simple-params.js'), -+ ), -+ readFileSync(require.resolve('./fix-hermes-v1-class-in-finally.js')), -+ readFileSync( -+ require.resolve('./fix-hermes-v1-super-in-object-accessor.js'), -+ ), - ].forEach(part => key.update(part)); - cacheKey = key.digest('hex'); - return cacheKey;