From d73b6f111057e09b9886e8edd2bc70fa56229489 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Thu, 21 Aug 2025 15:02:49 -0400 Subject: [PATCH 1/5] Update Flow to 0.261 (#34255) - 0.261 required to pull out a constant to preserve refinement - 0.259 needed some updated suppressions for hacky stuff --- package.json | 4 ++-- packages/react-client/src/ReactFlightReplyClient.js | 2 +- .../src/client/ReactFiberConfigDOM.js | 5 +++-- .../src/ReactFiberConfigFabric.js | 5 +++-- .../src/ReactFlightESMReferences.js | 2 +- .../src/ReactFlightParcelReferences.js | 2 +- .../src/ReactFlightTurbopackReferences.js | 2 +- .../src/ReactFlightWebpackReferences.js | 3 +-- packages/react-server/src/ReactFlightServer.js | 2 +- yarn.lock | 10 +++++----- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index d3e763a8c4b..7d191ee4e28 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,8 @@ "eslint-plugin-react-internal": "link:./scripts/eslint-rules", "fbjs-scripts": "^3.0.1", "filesize": "^6.0.1", - "flow-bin": "^0.258", - "flow-remove-types": "^2.258", + "flow-bin": "^0.261", + "flow-remove-types": "^2.261", "glob": "^7.1.6", "glob-stream": "^6.1.0", "google-closure-compiler": "^20230206.0.0", diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 22479aac044..f75f54f4ead 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -1183,7 +1183,7 @@ function bind(this: Function): Function { const referenceClosure = knownServerReferences.get(this); if (!referenceClosure) { - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] return FunctionBind.apply(this, arguments); } diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 4d1e60e53a6..76f59b1fc54 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -2991,7 +2991,8 @@ FragmentInstance.prototype.unobserveUsing = function ( this: FragmentInstanceType, observer: IntersectionObserver | ResizeObserver, ): void { - if (this._observers === null || !this._observers.has(observer)) { + const observers = this._observers; + if (observers === null || !observers.has(observer)) { if (__DEV__) { console.error( 'You are calling unobserveUsing() with an observer that is not being observed with this fragment ' + @@ -2999,7 +3000,7 @@ FragmentInstance.prototype.unobserveUsing = function ( ); } } else { - this._observers.delete(observer); + observers.delete(observer); traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer); } }; diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 4766c59b245..5ad58533624 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -651,7 +651,8 @@ FragmentInstance.prototype.unobserveUsing = function ( this: FragmentInstanceType, observer: IntersectionObserver, ): void { - if (this._observers === null || !this._observers.has(observer)) { + const observers = this._observers; + if (observers === null || !observers.has(observer)) { if (__DEV__) { console.error( 'You are calling unobserveUsing() with an observer that is not being observed with this fragment ' + @@ -659,7 +660,7 @@ FragmentInstance.prototype.unobserveUsing = function ( ); } } else { - this._observers.delete(observer); + observers.delete(observer); traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer); } }; diff --git a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js index 05ee9df9adf..b8a8749c102 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js @@ -49,7 +49,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference): any { - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { diff --git a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js index 48560ae6535..da7e1c0a00b 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js +++ b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js @@ -56,7 +56,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference): any { - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js index baa297cf33d..707a68a117e 100644 --- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js +++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js @@ -63,7 +63,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference): any { - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index c06e52a578d..707a68a117e 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -63,8 +63,7 @@ const FunctionBind = Function.prototype.bind; // $FlowFixMe[method-unbinding] const ArraySlice = Array.prototype.slice; function bind(this: ServerReference): any { - // $FlowFixMe[unsupported-syntax] - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] const newFn = FunctionBind.apply(this, arguments); if (this.$$typeof === SERVER_REFERENCE_TAG) { if (__DEV__) { diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index bc9c259b392..b8b3af14d59 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -408,7 +408,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) { emitConsoleChunk(request, methodName, owner, env, stack, args); } - // $FlowFixMe[prop-missing] + // $FlowFixMe[incompatible-call] return originalMethod.apply(this, arguments); }; if (originalName) { diff --git a/yarn.lock b/yarn.lock index c0a406642bd..f79e4479cb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9108,12 +9108,12 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flow-bin@^0.258: - version "0.258.1" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.258.1.tgz#5580e6085196800487dae67a71777b572d0c9552" - integrity sha512-v08KZw8jiwhA01V59kuCLd628kDJ0ZTX4jguh0P2rboF9rMlGmV8Rd+io1f1U9nWGoORTP2ZUI27X42jFA6eDQ== +flow-bin@^0.261: + version "0.261.2" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.261.2.tgz#8558c965950f8e38872ea21f66bf04932da2380b" + integrity sha512-lTYA05K4obAjyrrX4in3sLZyAECSFCDwQiGpZHJLm8ldCk+qcW11Wcxq/CdvyQAOPR7Kpb5BPRSaj4hwEXIAUw== -flow-remove-types@^2.258: +flow-remove-types@^2.261: version "2.279.0" resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.279.0.tgz#3a3388d9158eba0f82c40d80d31d9640b883a3f5" integrity sha512-bPFloMR/A2b/r/sIsf7Ix0LaMicCJNjwhXc4xEEQVzJCIz5u7C7XDaEOXOiqveKlCYK7DcBNn6R01Cbbc9gsYA== From 253abc78a1db0caab1693ecf9407d9bc10dd6bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 21 Aug 2025 17:50:20 -0400 Subject: [PATCH 2/5] [Flight] Transfer Debug Info from a synchronous Reference to another Chunk (#34229) --- .../react-client/src/ReactFlightClient.js | 96 ++++++++++++------- .../src/__tests__/ReactFlight-test.js | 26 +++++ 2 files changed, 85 insertions(+), 37 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 2eef555d36f..0e22e3f294f 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -496,13 +496,14 @@ function createErrorChunk( function wakeChunk( listeners: Array mixed)>, value: T, + chunk: SomeChunk, ): void { for (let i = 0; i < listeners.length; i++) { const listener = listeners[i]; if (typeof listener === 'function') { listener(value); } else { - fulfillReference(listener, value); + fulfillReference(listener, value, chunk); } } } @@ -555,7 +556,7 @@ function wakeChunkIfInitialized( ): void { switch (chunk.status) { case INITIALIZED: - wakeChunk(resolveListeners, chunk.value); + wakeChunk(resolveListeners, chunk.value, chunk); break; case BLOCKED: // It is possible that we're blocked on our own chunk if it's a cycle. @@ -569,7 +570,7 @@ function wakeChunkIfInitialized( if (cyclicHandler !== null) { // This reference points back to this chunk. We can resolve the cycle by // using the value from that handler. - fulfillReference(reference, cyclicHandler.value); + fulfillReference(reference, cyclicHandler.value, chunk); resolveListeners.splice(i, 1); i--; if (rejectListeners !== null) { @@ -637,7 +638,7 @@ function triggerErrorOnChunk( cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) { initializingChunk = cyclicChunk; } try { @@ -919,7 +920,7 @@ function initializeModelChunk(chunk: ResolvedModelChunk): void { cyclicChunk.value = null; cyclicChunk.reason = null; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) { initializingChunk = cyclicChunk; } @@ -938,7 +939,7 @@ function initializeModelChunk(chunk: ResolvedModelChunk): void { if (resolveListeners !== null) { cyclicChunk.value = null; cyclicChunk.reason = null; - wakeChunk(resolveListeners, value); + wakeChunk(resolveListeners, value, cyclicChunk); } if (initializingHandler !== null) { if (initializingHandler.errored) { @@ -961,7 +962,7 @@ function initializeModelChunk(chunk: ResolvedModelChunk): void { erroredChunk.reason = error; } finally { initializingHandler = prevHandler; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) { initializingChunk = prevChunk; } } @@ -1298,6 +1299,7 @@ function getChunk(response: Response, id: number): SomeChunk { function fulfillReference( reference: InitializationReference, value: any, + fulfilledChunk: SomeChunk, ): void { const {response, handler, parentObject, key, map, path} = reference; @@ -1376,6 +1378,8 @@ function fulfillReference( const mappedValue = map(response, value, parentObject, key); parentObject[key] = mappedValue; + transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue); + // If this is the root object for a model reference, where `handler.value` // is a stale `null`, the resolved value can be used directly. if (key === '' && handler.value === null) { @@ -1422,7 +1426,7 @@ function fulfillReference( initializedChunk.value = handler.value; initializedChunk.reason = handler.reason; // Used by streaming chunks if (resolveListeners !== null) { - wakeChunk(resolveListeners, handler.value); + wakeChunk(resolveListeners, handler.value, initializedChunk); } } } @@ -1669,7 +1673,7 @@ function loadServerReference, T>( initializedChunk.status = INITIALIZED; initializedChunk.value = handler.value; if (resolveListeners !== null) { - wakeChunk(resolveListeners, handler.value); + wakeChunk(resolveListeners, handler.value, initializedChunk); } } } @@ -1728,6 +1732,49 @@ function loadServerReference, T>( return (null: any); } +function transferReferencedDebugInfo( + parentChunk: null | SomeChunk, + referencedChunk: SomeChunk, + referencedValue: mixed, +): void { + if (__DEV__ && referencedChunk._debugInfo) { + const referencedDebugInfo = referencedChunk._debugInfo; + // If we have a direct reference to an object that was rendered by a synchronous + // server component, it might have some debug info about how it was rendered. + // We forward this to the underlying object. This might be a React Element or + // an Array fragment. + // If this was a string / number return value we lose the debug info. We choose + // that tradeoff to allow sync server components to return plain values and not + // use them as React Nodes necessarily. We could otherwise wrap them in a Lazy. + if ( + typeof referencedValue === 'object' && + referencedValue !== null && + (isArray(referencedValue) || + typeof referencedValue[ASYNC_ITERATOR] === 'function' || + referencedValue.$$typeof === REACT_ELEMENT_TYPE) && + !referencedValue._debugInfo + ) { + // We should maybe use a unique symbol for arrays but this is a React owned array. + // $FlowFixMe[prop-missing]: This should be added to elements. + Object.defineProperty((referencedValue: any), '_debugInfo', { + configurable: false, + enumerable: false, + writable: true, + value: referencedDebugInfo, + }); + } + // We also add it to the initializing chunk since the resolution of that promise is + // also blocked by these. By adding it to both we can track it even if the array/element + // is extracted, or if the root is rendered as is. + if (parentChunk !== null) { + const parentDebugInfo = + parentChunk._debugInfo || (parentChunk._debugInfo = []); + // $FlowFixMe[method-unbinding] + parentDebugInfo.push.apply(parentDebugInfo, referencedDebugInfo); + } + } +} + function getOutlinedModel( response: Response, reference: string, @@ -1825,32 +1872,7 @@ function getOutlinedModel( value = value[path[i]]; } const chunkValue = map(response, value, parentObject, key); - if (__DEV__ && chunk._debugInfo) { - // If we have a direct reference to an object that was rendered by a synchronous - // server component, it might have some debug info about how it was rendered. - // We forward this to the underlying object. This might be a React Element or - // an Array fragment. - // If this was a string / number return value we lose the debug info. We choose - // that tradeoff to allow sync server components to return plain values and not - // use them as React Nodes necessarily. We could otherwise wrap them in a Lazy. - if ( - typeof chunkValue === 'object' && - chunkValue !== null && - (isArray(chunkValue) || - typeof chunkValue[ASYNC_ITERATOR] === 'function' || - chunkValue.$$typeof === REACT_ELEMENT_TYPE) && - !chunkValue._debugInfo - ) { - // We should maybe use a unique symbol for arrays but this is a React owned array. - // $FlowFixMe[prop-missing]: This should be added to elements. - Object.defineProperty((chunkValue: any), '_debugInfo', { - configurable: false, - enumerable: false, - writable: true, - value: chunk._debugInfo, - }); - } - } + transferReferencedDebugInfo(initializingChunk, chunk, chunkValue); return chunkValue; case PENDING: case BLOCKED: @@ -2621,7 +2643,7 @@ function resolveStream>( cyclicChunk.status = BLOCKED; cyclicChunk.value = null; cyclicChunk.reason = null; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) { initializingChunk = cyclicChunk; } try { @@ -2650,7 +2672,7 @@ function resolveStream>( resolvedChunk.value = stream; resolvedChunk.reason = controller; if (resolveListeners !== null) { - wakeChunk(resolveListeners, chunk.value); + wakeChunk(resolveListeners, chunk.value, chunk); } } diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 0fd9b869c61..01f37319bf5 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -2957,6 +2957,19 @@ describe('ReactFlight', () => { transport: expect.arrayContaining([]), }, }, + { + time: 16, + }, + { + env: 'third-party', + key: null, + name: 'ThirdPartyAsyncIterableComponent', + props: {}, + stack: ' in Object. (at **)', + }, + { + time: 16, + }, {time: 17}, ] : undefined, @@ -2976,6 +2989,19 @@ describe('ReactFlight', () => { children: {}, }, }, + { + time: 19, + }, + { + time: 19, + }, + { + env: 'third-party', + key: null, + name: 'ThirdPartyAsyncIterableComponent', + props: {}, + stack: ' in Object. (at **)', + }, {time: 19}, ] : undefined, From 7d29ecbeb24327fdcd889fe184311bbeb0f04c30 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:53:34 -0700 Subject: [PATCH 3/5] [compiler] Aggregate error reporting, separate eslint rules (#34176) NOTE: this is a merged version of @mofeiZ's original PR along with my edits per offline discussion. The description is updated to reflect the latest approach. The key problem we're trying to solve with this PR is to allow developers more control over the compiler's various validations. The idea is to have a number of rules targeting a specific category of issues, such as enforcing immutability of props/state/etc or disallowing access to refs during render. We don't want to have to run the compiler again for every single rule, though, so @mofeiZ added an LRU cache that caches the full compilation output of N most recent files. The first rule to run on a given file will cause it to get cached, and then subsequent rules can pull from the cache, with each rule filtering down to its specific category of errors. For the categories, I went through and assigned a category roughly 1:1 to existing validations, and then used my judgement on some places that felt distinct enough to warrant a separate error. Every error in the compiler now has to supply both a severity (for legacy reasons) and a category (for ESLint). Each category corresponds 1:1 to a ESLint rule definition, so that the set of rules is automatically populated based on the defined categories. Categories include a flag for whether they should be in the recommended set or not. Note that as with the original version of this PR, only eslint-plugin-react-compiler is changed. We still have to update the main lint rule. ## Test Plan * Created a sample project using ESLint v9 and verified that the plugin can be configured correctly and detects errors * Edited `fixtures/eslint-v9` and introduced errors, verified that the w latest config changes in that fixture it correctly detects the errors * In the sample project, confirmed that the LRU caching is correctly caching compiler output, ie compiling files just once. Co-authored-by: Mofei Zhang --- babel.config-ts.js | 1 + .../src/CompilerError.ts | 362 ++++++- .../src/Entrypoint/Imports.ts | 4 +- .../src/Entrypoint/Program.ts | 5 + .../src/Entrypoint/Suppression.ts | 4 +- .../ValidateNoUntransformedReferences.ts | 13 +- .../src/HIR/BuildHIR.ts | 83 +- .../src/HIR/HIRBuilder.ts | 5 +- .../src/HIR/PrintHIR.ts | 6 +- .../src/Inference/AliasingEffects.ts | 2 +- .../src/Inference/DropManualMemoization.ts | 19 +- .../Inference/InferMutationAliasingEffects.ts | 16 +- .../src/Optimization/InlineJsxTransform.ts | 2 + .../ReactiveScopes/CodegenReactiveFunction.ts | 4 +- .../src/Transform/TransformFire.ts | 9 + .../src/Utils/Result.ts | 52 +- .../src/Validation/ValidateHooksUsage.ts | 7 +- .../ValidateLocalsNotReassignedAfterRender.ts | 7 +- .../ValidateMemoizedEffectDependencies.ts | 2 + .../Validation/ValidateNoCapitalizedCalls.ts | 3 + .../ValidateNoDerivedComputationsInEffects.ts | 2 + ...ValidateNoFreezingKnownMutableFunctions.ts | 4 +- .../ValidateNoImpureFunctionsInRender.ts | 4 +- .../Validation/ValidateNoJSXInTryStatement.ts | 4 +- .../Validation/ValidateNoRefAccessInRender.ts | 19 +- .../Validation/ValidateNoSetStateInEffects.ts | 4 +- .../Validation/ValidateNoSetStateInRender.ts | 9 +- .../ValidatePreservedManualMemoization.ts | 10 +- .../Validation/ValidateStaticComponents.ts | 4 +- .../src/Validation/ValidateUseMemo.ts | 7 +- ...nconditional-set-state-in-render.expect.md | 4 +- ...state-in-render-after-loop-break.expect.md | 2 +- ...l-set-state-in-render-after-loop.expect.md | 2 +- ...-state-in-render-with-loop-throw.expect.md | 2 +- ...r.unconditional-set-state-lambda.expect.md | 2 +- ...tate-nested-function-expressions.expect.md | 2 +- .../dynamic-gating-bailout-nopanic.expect.md | 2 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- ...e-after-useeffect-optional-chain.expect.md | 2 +- ...utate-after-useeffect-ref-access.expect.md | 2 +- .../mutate-after-useeffect.expect.md | 2 +- .../no-emit/retry-no-emit.expect.md | 2 +- ...in-catch-in-outer-try-with-catch.expect.md | 2 +- .../invalid-jsx-in-try-with-catch.expect.md | 2 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...e-after-useeffect-optional-chain.expect.md | 2 +- ...utate-after-useeffect-ref-access.expect.md | 2 +- .../mutate-after-useeffect.expect.md | 2 +- .../new-mutability/retry-no-emit.expect.md | 2 +- ...-constructed-component-in-render.expect.md | 2 +- ...ly-construct-component-in-render.expect.md | 2 +- ...y-constructed-component-function.expect.md | 2 +- ...onstructed-component-method-call.expect.md | 2 +- ...ically-constructed-component-new.expect.md | 2 +- .../babel-plugin-react-compiler/src/index.ts | 2 + .../__tests__/ImpureFunctionCallsRule-test.ts | 39 + .../__tests__/InvalidHooksRule-test.ts | 100 ++ .../__tests__/NoAmbiguousJsxRule-test.ts | 38 + .../__tests__/NoCapitalizedCallsRule-test.ts | 71 ++ .../__tests__/NoRefAccessInRender-tests.ts | 34 + .../__tests__/NoUnusedDirectivesRule-test.ts | 58 ++ .../__tests__/PluginTest-test.ts | 158 +++ .../__tests__/ReactCompilerRule-test.ts | 287 ------ .../ReactCompilerRuleTypescript-test.ts | 24 +- .../__tests__/shared-utils.ts | 76 ++ .../eslint-plugin-react-compiler/package.json | 4 +- .../eslint-plugin-react-compiler/src/index.ts | 21 +- .../src/rules/ReactCompilerRule.ts | 386 +++----- .../src/shared/RunReactCompiler.ts | 287 ++++++ compiler/packages/snap/src/compiler.ts | 11 +- compiler/yarn.lock | 901 +++++++++++++++++- fixtures/eslint-v9/eslint.config.ts | 17 +- fixtures/eslint-v9/package.json | 2 +- fixtures/eslint-v9/yarn.lock | 101 +- package.json | 1 + .../ESLintRuleExhaustiveDeps-test.js | 3 +- .../__tests__/ESLintRulesOfHooks-test.js | 3 +- .../__tests__/ReactCompilerRule-test.ts | 289 ------ .../ReactCompilerRuleTypescript-test.ts | 4 +- .../eslint-plugin-react-hooks/src/index.ts | 75 +- .../src/rules/ReactCompiler.ts | 359 ------- .../src/shared/ReactCompiler.ts | 216 +++++ .../src/shared/RunReactCompiler.ts | 281 ++++++ scripts/rollup/build.js | 1 + scripts/rollup/bundles.js | 23 +- yarn.lock | 232 +++-- 87 files changed, 3298 insertions(+), 1531 deletions(-) create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts delete mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts create mode 100644 compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts delete mode 100644 packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts delete mode 100644 packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts create mode 100644 packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts create mode 100644 packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts diff --git a/babel.config-ts.js b/babel.config-ts.js index 45efe2a8708..02ffb447b37 100644 --- a/babel.config-ts.js +++ b/babel.config-ts.js @@ -8,6 +8,7 @@ module.exports = { '@babel/plugin-syntax-jsx', '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-transform-class-properties', {loose: true}], + ['@babel/plugin-transform-private-methods', {loose: true}], '@babel/plugin-transform-classes', ], presets: [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 2055b25c0c9..5940dbb4fd6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -47,8 +47,9 @@ export enum ErrorSeverity { } export type CompilerDiagnosticOptions = { + category: ErrorCategory; severity: ErrorSeverity; - category: string; + reason: string; description: string; details: Array; suggestions?: Array | null | undefined; @@ -91,9 +92,10 @@ export type CompilerSuggestion = }; export type CompilerErrorDetailOptions = { + category: ErrorCategory; + severity: ErrorSeverity; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; @@ -119,8 +121,8 @@ export class CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } - get category(): CompilerDiagnosticOptions['category'] { - return this.options.category; + get reason(): CompilerDiagnosticOptions['reason'] { + return this.options.reason; } get description(): CompilerDiagnosticOptions['description'] { return this.options.description; @@ -131,6 +133,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get category(): ErrorCategory { + return this.options.category; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -148,7 +153,7 @@ export class CompilerDiagnostic { printErrorMessage(source: string, options: PrintErrorMessageOptions): string { const buffer = [ - printErrorSummary(this.severity, this.category), + printErrorSummary(this.severity, this.reason), '\n\n', this.description, ]; @@ -193,7 +198,7 @@ export class CompilerDiagnostic { } toString(): string { - const buffer = [printErrorSummary(this.severity, this.category)]; + const buffer = [printErrorSummary(this.severity, this.reason)]; if (this.description != null) { buffer.push(`. ${this.description}.`); } @@ -231,6 +236,9 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get category(): ErrorCategory { + return this.options.category; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -280,13 +288,14 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, + category: ErrorCategory.Invariant, severity: ErrorSeverity.Invariant, }), ); @@ -301,23 +310,28 @@ export class CompilerError extends Error { } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( - new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}), + new CompilerErrorDetail({ + ...options, + severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, + }), ); throw errors; } static throwInvalidJS( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, }), ); throw errors; @@ -337,13 +351,14 @@ export class CompilerError extends Error { } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, severity: ErrorSeverity.InvalidConfig, + category: ErrorCategory.Config, }), ); throw errors; @@ -407,6 +422,7 @@ export class CompilerError extends Error { push(options: CompilerErrorDetailOptions): CompilerErrorDetail { const detail = new CompilerErrorDetail({ + category: options.category, reason: options.reason, description: options.description ?? null, severity: options.severity, @@ -507,3 +523,327 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string { } return `${severityCategory}: ${message}`; } + +/** + * See getRuleForCategory() for how these map to ESLint rules + */ +export enum ErrorCategory { + // Checking for valid hooks usage (non conditional, non-first class, non reactive, etc) + Hooks = 'Hooks', + + // Checking for no capitalized calls (not definitively an error, hence separating) + CapitalizedCalls = 'CapitalizedCalls', + + // Checking for static components + StaticComponents = 'StaticComponents', + + // Checking for valid usage of manual memoization + UseMemo = 'UseMemo', + + // Checks that manual memoization is preserved + PreserveManualMemo = 'PreserveManualMemo', + + // Checking for no mutations of props, hook arguments, hook return values + Immutability = 'Immutability', + + // Checking for assignments to globals + Globals = 'Globals', + + // Checking for valid usage of refs, ie no access during render + Refs = 'Refs', + + // Checks for memoized effect deps + EffectDependencies = 'EffectDependencies', + + // Checks for no setState in effect bodies + EffectSetState = 'EffectSetState', + + EffectDerivationsOfState = 'EffectDerivationsOfState', + + // Validates against try/catch in place of error boundaries + ErrorBoundaries = 'ErrorBoundaries', + + // Checking for pure functions + Purity = 'Purity', + + // Validates against setState in render + RenderSetState = 'RenderSetState', + + // Internal invariants + Invariant = 'Invariant', + + // Todos + Todo = 'Todo', + + // Syntax errors + Syntax = 'Syntax', + + // Checks for use of unsupported syntax + UnsupportedSyntax = 'UnsupportedSyntax', + + // Config errors + Config = 'Config', + + // Gating error + Gating = 'Gating', + + // Suppressions + Suppression = 'Suppression', + + // Issues with auto deps + AutomaticEffectDependencies = 'AutomaticEffectDependencies', + + // Issues with `fire` + Fire = 'Fire', + + // fbt-specific issues + FBT = 'FBT', +} + +export type LintRule = { + // Stores the category the rule corresponds to, used to filter errors when reporting + category: ErrorCategory; + + /** + * The "name" of the rule as it will be used by developers to enable/disable, eg + * "eslint-disable-nest line " + */ + name: string; + + /** + * A description of the rule that appears somewhere in ESLint. This does not affect + * how error messages are formatted + */ + description: string; + + /** + * If true, this rule will automatically appear in the default, "recommended" ESLint + * rule set. Otherwise it will be part of an `allRules` export that developers can + * use to opt-in to showing output of all possible rules. + * + * NOTE: not all validations are enabled by default! Setting this flag only affects + * whether a given rule is part of the recommended set. The corresponding validation + * also should be enabled by default if you want the error to actually show up! + */ + recommended: boolean; +}; + +export function getRuleForCategory(category: ErrorCategory): LintRule { + switch (category) { + case ErrorCategory.AutomaticEffectDependencies: { + return { + category, + name: 'automatic-effect-dependencies', + description: + 'Verifies that automatic effect dependencies are compiled if opted-in', + recommended: true, + }; + } + case ErrorCategory.CapitalizedCalls: { + return { + category, + name: 'capitalized-calls', + description: + 'Validates against calling capitalized functions/methods instead of using JSX', + recommended: false, + }; + } + case ErrorCategory.Config: { + return { + category, + name: 'config', + description: 'Validates the configuration', + recommended: true, + }; + } + case ErrorCategory.EffectDependencies: { + return { + category, + name: 'memoized-effect-dependencies', + description: 'Validates that effect dependencies are memoized', + recommended: false, + }; + } + case ErrorCategory.EffectDerivationsOfState: { + return { + category, + name: 'no-deriving-state-in-effects', + description: + 'Validates against deriving values from state in an effect', + recommended: false, + }; + } + case ErrorCategory.EffectSetState: { + return { + category, + name: 'set-state-in-effect', + description: + 'Validates against calling setState synchronously in an effect', + recommended: true, + }; + } + case ErrorCategory.ErrorBoundaries: { + return { + category, + name: 'error-boundaries', + description: + 'Validates usage of error boundaries instead of try/catch for errors in JSX', + recommended: true, + }; + } + case ErrorCategory.FBT: { + return { + category, + name: 'fbt', + description: 'Validates usage of fbt', + recommended: false, + }; + } + case ErrorCategory.Fire: { + return { + category, + name: 'fire', + description: 'Validates usage of `fire`', + recommended: false, + }; + } + case ErrorCategory.Gating: { + return { + category, + name: 'gating', + description: 'Validates configuration of gating mode', + recommended: true, + }; + } + case ErrorCategory.Globals: { + return { + category, + name: 'globals', + description: + 'Validates against assignment/mutation of globals during render', + recommended: true, + }; + } + case ErrorCategory.Hooks: { + return { + category, + name: 'hooks', + description: 'Validates the rules of hooks', + /** + * TODO: the "Hooks" rule largely reimplements the "rules-of-hooks" non-compiler rule. + * We need to dedeupe these (moving the remaining bits into the compiler) and then enable + * this rule. + */ + recommended: false, + }; + } + case ErrorCategory.Immutability: { + return { + category, + name: 'immutability', + description: + 'Validates that immutable values (props, state, etc) are not mutated', + recommended: true, + }; + } + case ErrorCategory.Invariant: { + return { + category, + name: 'invariant', + description: 'Internal invariants', + recommended: false, + }; + } + case ErrorCategory.PreserveManualMemo: { + return { + category, + name: 'preserve-manual-memoization', + description: + 'Validates that existing manual memoized is preserved by the compiler', + recommended: true, + }; + } + case ErrorCategory.Purity: { + return { + category, + name: 'purity', + description: + 'Validates that the component/hook is pure, and does not call known-impure functions', + recommended: true, + }; + } + case ErrorCategory.Refs: { + return { + category, + name: 'refs', + description: + 'Validates correct usage of refs, not reading/writing during render', + recommended: true, + }; + } + case ErrorCategory.RenderSetState: { + return { + category, + name: 'set-state-in-render', + description: 'Validates against setting state during render', + recommended: true, + }; + } + case ErrorCategory.StaticComponents: { + return { + category, + name: 'static-components', + description: + 'Validates that components are static, not recreated every render', + recommended: true, + }; + } + case ErrorCategory.Suppression: { + return { + category, + name: 'rule-suppression', + description: 'Validates against suppression of other rules', + recommended: false, + }; + } + case ErrorCategory.Syntax: { + return { + category, + name: 'syntax', + description: 'Validates against invalid syntax', + recommended: false, + }; + } + case ErrorCategory.Todo: { + return { + category, + name: 'todo', + description: 'Unimplemented features', + recommended: false, + }; + } + case ErrorCategory.UnsupportedSyntax: { + return { + category, + name: 'unsupported-syntax', + description: 'Validates against syntax that we do not plan to support', + recommended: true, + }; + } + case ErrorCategory.UseMemo: { + return { + category, + name: 'use-memo', + description: 'Validates usage of the useMemo() hook', + recommended: true, + }; + } + default: { + assertExhaustive(category, `Unsupported category ${category}`); + } + } +} + +export const LintRules: Array = Object.keys(ErrorCategory).map( + category => getRuleForCategory(category as any), +); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72c..9653c49576a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -9,7 +9,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; import {Scope as BabelScope} from '@babel/traverse'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import { EnvironmentConfig, GeneratedSource, @@ -38,6 +38,7 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ + category: ErrorCategory.Todo, severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, @@ -205,6 +206,7 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ + category: ErrorCategory.Todo, severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a56..92151501622 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -10,6 +10,7 @@ import * as t from '@babel/types'; import { CompilerError, CompilerErrorDetail, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; @@ -105,6 +106,7 @@ function findDirectivesDynamicGating( reason: `Dynamic gating directive is not a valid JavaScript identifier`, description: `Found '${directive.value.value}'`, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Gating, loc: directive.loc ?? null, suggestions: null, }); @@ -121,6 +123,7 @@ function findDirectivesDynamicGating( .map(r => r.directive.value.value) .join(', ')}]`, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Gating, loc: result[0].directive.loc ?? null, suggestions: null, }); @@ -456,6 +459,7 @@ export function compileProgram( reason: 'Unexpected compiled functions when module scope opt-out is present', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: null, }), ); @@ -811,6 +815,7 @@ function shouldSkipCompilation( description: "When the 'sources' config options is specified, the React compiler will only compile files with a name", severity: ErrorSeverity.InvalidConfig, + category: ErrorCategory.Config, loc: null, }), ); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f1..a0d06f96f0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,6 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; @@ -183,9 +184,10 @@ export function suppressionsToCompilerError( } error.pushDiagnostic( CompilerDiagnostic.create({ - category: reason, + reason: reason, description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Suppression, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c6..beaaff0f79a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -13,7 +13,11 @@ import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import { + CompilerDiagnostic, + CompilerDiagnosticOptions, + ErrorCategory, +} from '../CompilerError'; function throwInvalidReact( options: Omit, @@ -92,7 +96,8 @@ function assertValidEffectImportReference( */ throwInvalidReact( { - category: + category: ErrorCategory.AutomaticEffectDependencies, + reason: 'Cannot infer dependencies of this effect. This will break your build!', description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + @@ -123,8 +128,8 @@ function assertValidFireImportReference( ); throwInvalidReact( { - category: - '[Fire] Untransformed reference to compiler-required feature.', + category: ErrorCategory.Fire, + reason: '[Fire] Untransformed reference to compiler-required feature.', description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 3b11670146b..77f2a04e7cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -108,7 +109,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.Invariant, - category: 'Could not find binding', + category: ErrorCategory.Invariant, + reason: 'Could not find binding', description: `[BuildHIR] Could not find binding for param \`${param.node.name}\`.`, }).withDetail({ kind: 'error', @@ -172,7 +174,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + category: ErrorCategory.Todo, + reason: `Handle ${param.node.type} parameters`, description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', @@ -203,7 +206,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + category: ErrorCategory.Syntax, + reason: `Unexpected function body kind`, description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', @@ -273,6 +277,7 @@ function lowerStatement( reason: '(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -460,6 +465,7 @@ function lowerStatement( } else if (!binding.path.isVariableDeclarator()) { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Unsupported declaration type for hoisting', description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`, suggestions: null, @@ -469,6 +475,7 @@ function lowerStatement( } else { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Handle non-const declarations for hoisting', description: `variable "${binding.identifier.name}" declared with ${binding.kind}`, suggestions: null, @@ -549,6 +556,7 @@ function lowerStatement( reason: '(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -621,6 +629,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -772,6 +781,7 @@ function lowerStatement( builder.errors.push({ reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: case_.node.loc ?? null, suggestions: null, }); @@ -844,6 +854,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -872,6 +883,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: id.node.loc ?? null, suggestions: null, }); @@ -889,6 +901,7 @@ function lowerStatement( builder.errors.push({ reason: `Expect \`const\` declaration not to be reassigned`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: id.node.loc ?? null, suggestions: [ { @@ -936,6 +949,7 @@ function lowerStatement( reason: `Expected variable declaration to be an identifier if no initializer was provided`, description: `Got a \`${id.type}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1044,6 +1058,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle for-await loops`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1276,6 +1291,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1285,6 +1301,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1378,6 +1395,7 @@ function lowerStatement( reason: `JavaScript 'with' syntax is not supported`, description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1398,6 +1416,7 @@ function lowerStatement( reason: 'Inline `class` declarations are not supported', description: `Move class declarations outside of components/hooks`, severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1427,6 +1446,7 @@ function lowerStatement( reason: 'JavaScript `import` and `export` statements may only appear at the top level of a module', severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1442,6 +1462,7 @@ function lowerStatement( reason: 'TypeScript `namespace` statements may only appear at the top level of a module', severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1520,6 +1541,7 @@ function lowerObjectPropertyKey( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: key.node.loc ?? null, suggestions: null, }); @@ -1545,6 +1567,7 @@ function lowerObjectPropertyKey( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: key.node.loc ?? null, suggestions: null, }); @@ -1602,6 +1625,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valuePath.node.loc ?? null, suggestions: null, }); @@ -1628,6 +1652,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyPath.node.loc ?? null, suggestions: null, }); @@ -1649,6 +1674,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyPath.node.loc ?? null, suggestions: null, }); @@ -1682,6 +1708,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: element.node.loc ?? null, suggestions: null, }); @@ -1702,6 +1729,7 @@ function lowerExpression( reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, description: `Got a \`${calleePath.node.type}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: calleePath.node.loc ?? null, suggestions: null, }); @@ -1728,6 +1756,7 @@ function lowerExpression( builder.errors.push({ reason: `Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported). This error is likely caused by a bug in React Compiler. Please file an issue`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: calleePath.node.loc ?? null, suggestions: null, }); @@ -1762,6 +1791,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: leftPath.node.loc ?? null, suggestions: null, }); @@ -1774,6 +1804,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Pipe operator not supported`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: leftPath.node.loc ?? null, suggestions: null, }); @@ -1803,6 +1834,7 @@ function lowerExpression( builder.errors.push({ reason: `Expected sequence expression to have at least one expression`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2015,6 +2047,7 @@ function lowerExpression( reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`, description: `Expected an LVal, got: ${left.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: left.node.loc ?? null, suggestions: null, }); @@ -2043,6 +2076,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2142,6 +2176,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2181,6 +2216,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: attribute.node.loc ?? null, suggestions: null, }); @@ -2194,6 +2230,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: namePath.node.loc ?? null, suggestions: null, }); @@ -2224,6 +2261,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valueExpr.node?.loc ?? null, suggestions: null, }); @@ -2234,6 +2272,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valueExpr.node.loc ?? null, suggestions: null, }); @@ -2291,7 +2330,8 @@ function lowerExpression( if (locations.length > 1) { CompilerError.throwDiagnostic({ severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', + category: ErrorCategory.FBT, + reason: 'Support duplicate fbt tags', description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, details: locations.map(loc => { return { @@ -2352,6 +2392,7 @@ function lowerExpression( reason: '(BuildHIR::lowerExpression) Handle tagged template with interpolations', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2370,6 +2411,7 @@ function lowerExpression( reason: '(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2392,6 +2434,7 @@ function lowerExpression( builder.errors.push({ reason: `Unexpected quasi and subexpression lengths in template literal`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2402,6 +2445,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2444,6 +2488,7 @@ function lowerExpression( builder.errors.push({ reason: `Only object properties can be deleted`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: [ { @@ -2459,6 +2504,7 @@ function lowerExpression( builder.errors.push({ reason: `Throw expressions are not supported`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: [ { @@ -2580,6 +2626,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2588,6 +2635,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2608,6 +2656,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: exprLoc, suggestions: null, }); @@ -2617,6 +2666,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprLoc, suggestions: null, }); @@ -2672,6 +2722,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2681,6 +2732,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2978,6 +3030,7 @@ function lowerReorderableExpression( builder.errors.push({ reason: `(BuildHIR::node.lowerReorderableExpression) Expression type \`${expr.type}\` cannot be safely reordered`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -3174,6 +3227,7 @@ function lowerArguments( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: argPath.node.loc ?? null, suggestions: null, }); @@ -3209,6 +3263,7 @@ function lowerMemberExpression( builder.errors.push({ reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyNode.node.loc ?? null, suggestions: null, }); @@ -3230,6 +3285,7 @@ function lowerMemberExpression( builder.errors.push({ reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyNode.node.loc ?? null, suggestions: null, }); @@ -3289,6 +3345,7 @@ function lowerJsxElementName( reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, description: `Got \`${namespace}\` : \`${name}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3303,6 +3360,7 @@ function lowerJsxElementName( builder.errors.push({ reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3401,6 +3459,7 @@ function lowerJsxElement( builder.errors.push({ reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3588,6 +3647,7 @@ function lowerIdentifier( description: 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3644,6 +3704,7 @@ function lowerIdentifierForAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: path.node.loc ?? null, suggestions: null, }); @@ -3656,6 +3717,7 @@ function lowerIdentifierForAssignment( builder.errors.push({ reason: `Cannot reassign a \`const\` variable`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3713,6 +3775,7 @@ function lowerAssignment( builder.errors.push({ reason: `Expected \`const\` declaration not to be reassigned`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: lvalue.node.loc ?? null, suggestions: null, }); @@ -3727,6 +3790,7 @@ function lowerAssignment( builder.errors.push({ reason: `Unexpected context variable kind`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: lvalue.node.loc ?? null, suggestions: null, }); @@ -3798,6 +3862,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -3810,6 +3875,7 @@ function lowerAssignment( reason: '(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -3875,6 +3941,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -3914,6 +3981,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -3987,6 +4055,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: argument.node.loc ?? null, suggestions: null, }); @@ -4018,6 +4087,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: property.node.loc ?? GeneratedSource, @@ -4035,6 +4105,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -4044,6 +4115,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -4058,6 +4130,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: element.node.loc ?? null, suggestions: null, }); @@ -4080,6 +4153,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -4229,6 +4303,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: lvaluePath.node.loc ?? null, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea3615..272b5fc0752 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -310,7 +310,8 @@ export default class HIRBuilder { if (node.name === 'fbt') { CompilerError.throwDiagnostic({ severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', + category: ErrorCategory.FBT, + reason: 'Support local variables named `fbt`', description: 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', details: [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 34ce7f7694b..7d2b0b234c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -986,13 +986,13 @@ export function printAliasingEffect(effect: AliasingEffect): string { return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`; } case 'MutateFrozen': { - return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'MutateGlobal': { - return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Impure': { - return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Render': { return `Render ${printPlaceForAliasEffect(effect.place)}`; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts index 80694a7a784..7f30e25a5c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts @@ -231,7 +231,7 @@ export function hashEffect(effect: AliasingEffect): string { effect.kind, effect.place.identifier.id, effect.error.severity, - effect.error.category, + effect.error.reason, effect.error.description, printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource), ].join(':'); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22a..412efcfe7ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -11,6 +11,7 @@ import { ErrorSeverity, SourceLocation, } from '..'; +import {ErrorCategory} from '../CompilerError'; import { CallExpression, Effect, @@ -300,8 +301,9 @@ function extractManualMemoizationArgs( if (fnPlace == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, + reason: `Expected a callback function to be passed to ${kind}`, description: `Expected a callback function to be passed to ${kind}`, suggestions: null, }).withDetail({ @@ -315,8 +317,9 @@ function extractManualMemoizationArgs( if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, + reason: `Unexpected spread argument to ${kind}`, description: `Unexpected spread argument to ${kind}`, suggestions: null, }).withDetail({ @@ -335,8 +338,9 @@ function extractManualMemoizationArgs( if (maybeDepsList == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, + reason: `Expected the dependency list for ${kind} to be an array literal`, description: `Expected the dependency list for ${kind} to be an array literal`, suggestions: null, }).withDetail({ @@ -353,8 +357,9 @@ function extractManualMemoizationArgs( if (maybeDep == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, suggestions: null, }).withDetail({ @@ -459,7 +464,8 @@ export function dropManualMemoization( errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', + category: ErrorCategory.UseMemo, + reason: 'useMemo() callbacks must return a value', description: `This ${ manualMemo.loadInstr.value.kind === 'PropertyLoad' ? 'React.useMemo' @@ -498,8 +504,9 @@ export function dropManualMemoization( if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, + reason: `Expected the first argument to be an inline function expression`, description: `Expected the first argument to be an inline function expression`, suggestions: [], }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index d051fb37250..36f32213eab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -69,6 +69,7 @@ import { hashEffect, MutationReason, } from './AliasingEffects'; +import {ErrorCategory} from '../CompilerError'; const DEBUG = false; @@ -452,8 +453,9 @@ function applySignature( ? `\`${effect.value.identifier.name.value}\`` : 'value'; const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', + reason: 'This value cannot be modified', description: `${reason}.`, }).withDetail({ kind: 'error', @@ -1036,8 +1038,9 @@ function applyEffect( effect.value.identifier.declarationId, ); const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', + reason: 'Cannot access variable before it is declared', description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, }); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { @@ -1075,8 +1078,9 @@ function applyEffect( ? `\`${effect.value.identifier.name.value}\`` : 'value'; const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', + reason: 'This value cannot be modified', description: `${reason}.`, }).withDetail({ kind: 'error', @@ -2033,8 +2037,9 @@ function computeSignatureForInstruction( kind: 'MutateGlobal', place: value.value, error: CompilerDiagnostic.create({ + category: ErrorCategory.Globals, severity: ErrorSeverity.InvalidReact, - category: + reason: 'Cannot reassign variables declared outside of the component/hook', description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, }).withDetail({ @@ -2132,8 +2137,9 @@ function computeEffectsForLegacySignature( kind: 'Impure', place: receiver, error: CompilerDiagnostic.create({ + category: ErrorCategory.Purity, severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', + reason: 'Cannot call impure function during render', description: (signature.canonicalName != null ? `\`${signature.canonicalName}\` is an impure function. ` diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts index 91e2ce06925..3588cf32f9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -42,6 +42,7 @@ import { mapInstructionValueOperands, mapTerminalOperands, } from '../HIR/visitors'; +import {ErrorCategory} from '../CompilerError'; type InlinedJsxDeclarationMap = Map< DeclarationId, @@ -83,6 +84,7 @@ export function inlineJsxTransform( kind: 'CompileDiagnostic', fnLoc: null, detail: { + category: ErrorCategory.Todo, reason: 'JSX Inlining is not supported on value blocks', loc: instr.loc, }, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index f7da5229548..c02a41f8f0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -13,7 +13,7 @@ import { pruneUnusedLabels, renameVariables, } from '.'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import {Environment, ExternalFunction} from '../HIR'; import { ArrayPattern, @@ -2185,6 +2185,7 @@ function codegenInstructionValue( (declarator.id as t.Identifier).name }'`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: declarator.loc ?? null, suggestions: null, }); @@ -2193,6 +2194,7 @@ function codegenInstructionValue( cx.errors.push({ reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.loc ?? null, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0f..393f95d94c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCategory} from '../CompilerError'; /* * TODO(jmbrown): @@ -133,6 +134,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description: null, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, reason: '[InsertFire] No LoadGlobal found for useEffect call', suggestions: null, }); @@ -179,6 +181,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -189,6 +192,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -223,6 +227,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description: null, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, reason: '[InsertFire] No loadLocal found for fire call argument', suggestions: null, @@ -246,6 +251,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -264,6 +270,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -395,6 +402,7 @@ function ensureNoRemainingCalleeCaptures( this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -411,6 +419,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', + category: ErrorCategory.Fire, severity: ErrorSeverity.Invariant, reason: CANNOT_COMPILE_FIRE, suggestions: null, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts index 85fb3922d66..32e3ec82c59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts @@ -90,10 +90,13 @@ export function Ok(val: T): OkImpl { } class OkImpl implements Result { - constructor(private val: T) {} + #val: T; + constructor(val: T) { + this.#val = val; + } map(fn: (val: T) => U): Result { - return new OkImpl(fn(this.val)); + return new OkImpl(fn(this.#val)); } mapErr(_fn: (val: never) => F): Result { @@ -101,15 +104,15 @@ class OkImpl implements Result { } mapOr(_fallback: U, fn: (val: T) => U): U { - return fn(this.val); + return fn(this.#val); } mapOrElse(_fallback: () => U, fn: (val: T) => U): U { - return fn(this.val); + return fn(this.#val); } andThen(fn: (val: T) => Result): Result { - return fn(this.val); + return fn(this.#val); } and(res: Result): Result { @@ -133,30 +136,30 @@ class OkImpl implements Result { } expect(_msg: string): T { - return this.val; + return this.#val; } expectErr(msg: string): never { - throw new Error(`${msg}: ${this.val}`); + throw new Error(`${msg}: ${this.#val}`); } unwrap(): T { - return this.val; + return this.#val; } unwrapOr(_fallback: T): T { - return this.val; + return this.#val; } unwrapOrElse(_fallback: (val: never) => T): T { - return this.val; + return this.#val; } unwrapErr(): never { - if (this.val instanceof Error) { - throw this.val; + if (this.#val instanceof Error) { + throw this.#val; } - throw new Error(`Can't unwrap \`Ok\` to \`Err\`: ${this.val}`); + throw new Error(`Can't unwrap \`Ok\` to \`Err\`: ${this.#val}`); } } @@ -165,14 +168,17 @@ export function Err(val: E): ErrImpl { } class ErrImpl implements Result { - constructor(private val: E) {} + #val: E; + constructor(val: E) { + this.#val = val; + } map(_fn: (val: never) => U): Result { return this; } mapErr(fn: (val: E) => F): Result { - return new ErrImpl(fn(this.val)); + return new ErrImpl(fn(this.#val)); } mapOr(fallback: U, _fn: (val: never) => U): U { @@ -196,7 +202,7 @@ class ErrImpl implements Result { } orElse(fn: (val: E) => ErrImpl): Result { - return fn(this.val); + return fn(this.#val); } isOk(): this is OkImpl { @@ -208,18 +214,18 @@ class ErrImpl implements Result { } expect(msg: string): never { - throw new Error(`${msg}: ${this.val}`); + throw new Error(`${msg}: ${this.#val}`); } expectErr(_msg: string): E { - return this.val; + return this.#val; } unwrap(): never { - if (this.val instanceof Error) { - throw this.val; + if (this.#val instanceof Error) { + throw this.#val; } - throw new Error(`Can't unwrap \`Err\` to \`Ok\`: ${this.val}`); + throw new Error(`Can't unwrap \`Err\` to \`Ok\`: ${this.#val}`); } unwrapOr(fallback: T): T { @@ -227,10 +233,10 @@ class ErrImpl implements Result { } unwrapOrElse(fallback: (val: E) => T): T { - return fallback(this.val); + return fallback(this.#val); } unwrapErr(): E { - return this.val; + return this.#val; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339ce..af596155253 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -9,6 +9,7 @@ import * as t from '@babel/types'; import { CompilerError, CompilerErrorDetail, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; @@ -124,6 +125,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, description: null, reason, loc: place.loc, @@ -140,6 +142,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, description: null, reason: 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', @@ -157,6 +160,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, description: null, reason: 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', @@ -424,7 +428,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -448,6 +452,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { if (hookKind != null) { errors.pushErrorDetail( new CompilerErrorDetail({ + category: ErrorCategory.Hooks, severity: ErrorSeverity.InvalidReact, reason: 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 163d645957c..e1ed71049df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -36,8 +37,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { : 'variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + reason: 'Cannot reassign variable after render completes', description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -91,8 +93,9 @@ function getContextReassignment( : 'variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + reason: 'Cannot reassign variable in async function', description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb15123..186641c3f20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -6,6 +6,7 @@ */ import {CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import { Identifier, Instruction, @@ -108,6 +109,7 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ + category: ErrorCategory.EffectDependencies, reason: 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', description: null, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2d..d0cf41a13c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -6,6 +6,7 @@ */ import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; import {Result} from '../Utils/Result'; @@ -56,6 +57,7 @@ export function validateNoCapitalizedCalls( const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { CompilerError.throwInvalidReact({ + category: ErrorCategory.CapitalizedCalls, reason, description: `${calleeName} may be a component.`, loc: value.loc, @@ -79,6 +81,7 @@ export function validateNoCapitalizedCalls( const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { errors.push({ + category: ErrorCategory.CapitalizedCalls, severity: ErrorSeverity.InvalidReact, reason, description: `${propertyName} may be a component.`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4a..f1fa5aec407 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -6,6 +6,7 @@ */ import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {ErrorCategory} from '../CompilerError'; import { ArrayExpression, BlockId, @@ -219,6 +220,7 @@ function validateEffect( for (const loc of setStateLocations) { errors.push({ + category: ErrorCategory.EffectDerivationsOfState, reason: 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', description: null, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index f3543e113a1..f49a9a0a475 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -64,8 +65,9 @@ export function validateNoFreezingKnownMutableFunctions( : 'a local variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + reason: 'Cannot modify local variables after render completes', description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index e7f8b1c0e5b..868352e050e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects'; import {Result} from '../Utils/Result'; @@ -36,7 +37,8 @@ export function validateNoImpureFunctionsInRender( if (signature != null && signature.impure === true) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', + category: ErrorCategory.Purity, + reason: 'Cannot call impure function during render', description: (signature.canonicalName != null ? `\`${signature.canonicalName}\` is an impure function. ` diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e9..158935f9e6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {BlockId, HIRFunction} from '../HIR'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -36,8 +37,9 @@ export function validateNoJSXInTryStatement( case 'JsxFragment': { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.ErrorBoundaries, severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', + reason: 'Avoid constructing JSX within try/catch', description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, }).withDetail({ kind: 'error', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f48..679d355e321 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -468,8 +469,9 @@ function validateNoRefAccessInRenderImpl( didError = true; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -731,8 +733,9 @@ function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -755,8 +758,9 @@ function validateNoRefValueAccess( ) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -781,8 +785,9 @@ function validateNoRefPassedToFunction( ) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -803,8 +808,9 @@ function validateNoRefUpdate( if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -824,8 +830,9 @@ function validateNoDirectRefValueAccess( if (type?.kind === 'RefValue') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380cd..4a24098a9fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -96,7 +97,8 @@ export function validateNoSetStateInEffects( if (setState !== undefined) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.EffectSetState, + reason: 'Calling setState synchronously within an effect can trigger cascading renders', description: 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c68..2ee9ab64b7e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; @@ -128,7 +129,8 @@ function validateNoSetStateInRenderImpl( if (activeManualMemoId !== null) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.RenderSetState, + reason: 'Calling setState from useMemo may trigger an infinite loop', description: 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', @@ -143,7 +145,8 @@ function validateNoSetStateInRenderImpl( } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.RenderSetState, + reason: 'Calling setState during render may trigger an infinite loop', description: 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', @@ -152,7 +155,7 @@ function validateNoSetStateInRenderImpl( }).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() in render', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247dab..516ca232e9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -281,8 +282,9 @@ function validateInferredDep( } errorState.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + reason: 'Compilation skipped because existing memoization could not be preserved', description: [ 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', @@ -535,8 +537,9 @@ class Visitor extends ReactiveFunctionVisitor { ) { state.errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + reason: 'Compilation skipped because existing memoization could not be preserved', description: [ 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', @@ -583,8 +586,9 @@ class Visitor extends ReactiveFunctionVisitor { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + reason: 'Compilation skipped because existing memoization could not be preserved', description: [ 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b45..375b8e8f011 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; @@ -65,8 +66,9 @@ export function validateStaticComponents( if (location != null) { error.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.StaticComponents, severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + reason: 'Cannot create components during render', description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89f..3146bbea38a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; @@ -74,8 +75,9 @@ export function validateUseMemo(fn: HIRFunction): Result { : firstParam.place.loc; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', + reason: 'useMemo() callbacks may not accept parameters', description: 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', suggestions: null, @@ -90,8 +92,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: + reason: 'useMemo() callbacks may not be async or generator functions', description: 'useMemo() callbacks are called once and must synchronously return a value.', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418e..c4adc4c9bfb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() in render 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() in render 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c5..3a5990e1624 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() in render 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf89..c2beb3424a1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() in render 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572e..faef4dd5995 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() in render 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56fd..9d9c741f7ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() in render 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3fe..07dd0b0a17e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() in render 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md index 42d988804b7..58e85caf84f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"severity":"CannotPreserveMemoization","category":"Compilation skipped because existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","reason":"Compilation skipped because existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f573..7f8f1d43310 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"category":"Gating","reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md index d3a6e5f0d71..14bd646bb15 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d3b746b1f8a..2f930964acd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","severity":"InvalidReact","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 1ecf4778d79..9a8a2a78e06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1a..7ecd65382cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 6583f0a4e2d..5c7c4360e7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -65,7 +65,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","severity":"InvalidReact","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index b6e9e87ada4..88d4849b4f6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -42,7 +42,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","severity":"InvalidReact","reason":"Avoid constructing JSX within try/catch","description":"React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef273..9107c896cc0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e5..cb5203c8816 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"EffectSetState","reason":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md index 5021e7978d9..6a9b4d98e28 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md @@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":346},"end":{"line":9,"column":49,"index":393},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":368},"end":{"line":9,"column":27,"index":371},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md index 7bcc917447a..00473c8eec7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","severity":"InvalidReact","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md index dd275b857f3..825bc4a1df0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":191},"end":{"line":8,"column":14,"index":242},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":222},"end":{"line":7,"column":16,"index":225},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367e..7d490de8c6a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 1224a5b9cf9..c0cac509c88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -50,7 +50,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index 7bec7f73b55..337997caa59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index e7f9ad7f30f..019beccdc86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -37,7 +37,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index f6220645ef8..f673b277798 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -41,7 +41,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index 4a72cdd8555..a44cf6a9f0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 34244892756..2830d70d95c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -12,9 +12,11 @@ export { CompilerDiagnostic, CompilerSuggestionOperation, ErrorSeverity, + LintRules, type CompilerErrorDetailOptions, type CompilerDiagnosticOptions, type CompilerDiagnosticDetail, + type LintRule, } from './CompilerError'; export { compileFn as compile, diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 00000000000..a745397078a --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,39 @@ +/** + * 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. + */ + +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no impure function calls rule', + allRules[getRuleForCategory(ErrorCategory.Purity).name], + { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError('Cannot call impure function during render'), + makeTestCaseError('Cannot call impure function during render'), + makeTestCaseError('Cannot call impure function during render'), + ], + }, + ], + }, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 00000000000..0ba165011c6 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,100 @@ +/** + * 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. + */ + +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, makeTestCaseError, testRule} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'rules-of-hooks', + allRules[getRuleForCategory(ErrorCategory.Hooks).name], + { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [ + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + ], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [ + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + ], + }, + ], + }, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 00000000000..9a09f4c4e05 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,38 @@ +/** + * 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. + */ + +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no ambiguous JSX rule', + allRules[getRuleForCategory(ErrorCategory.ErrorBoundaries).name], + { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError('Avoid constructing JSX within try/catch')], + }, + ], + }, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 00000000000..c361670e974 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,71 @@ +/** + * 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. + */ +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, makeTestCaseError, testRule} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no-capitalized-calls', + allRules[getRuleForCategory(ErrorCategory.CapitalizedCalls).name], + { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + ], + }, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 00000000000..9042980a807 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,34 @@ +/** + * 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. + */ + +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no ref access in render rule', + allRules[getRuleForCategory(ErrorCategory.Refs).name], + { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError('Cannot access refs during render')], + }, + ], + }, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 00000000000..77f6dd93fbf --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * 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. + */ + +import {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 00000000000..6efd069aafa --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,158 @@ +/** + * 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. + */ + +import { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import { + normalizeIndent, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + // TODO: actually return multiple diagnostics in this case + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [makeTestCaseError('useMemo() callbacks must return a value')], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649d..00000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * 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. - */ - -import {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea68526..87baf724e12 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 00000000000..2ab24581d8d --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,76 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import {type ErrorCategory} from 'babel-plugin-react-compiler/src/CompilerError'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export function makeTestCaseError(reason: string): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033d..e5402611e2b 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101ef..1a339e83310 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,26 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import {allRules, recommendedRules} from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, -}; - const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: allRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f85..d2e8fdd42e0 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import { + LintRules, + type LintRule, +} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,263 +75,143 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { - meta: { - type: 'problem', - docs: { - description: 'Surfaces diagnostics from React Forget', - recommended: true, - }, - fixable: 'code', - hasSuggestions: true, - // validation is done at runtime with zod - schema: [{type: 'object', additionalProperties: true}], - }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; + return true; } + } + return false; +} - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +function makeRule(rule: LintRule): Rule.RuleModule { + const create = (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.category === rule.category) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) ) { // If Flow already caught this error, we don't need to report it again. - return; + continue; } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + /* + * TODO: if multiple rules report the same linter category, + * we should deduplicate them with a "reported" set + */ + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; } + return {}; + }; - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } + return { + meta: { + type: 'problem', + docs: { + description: rule.description, + recommended: rule.recommended, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create, + }; +} - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer): Rule.Fix { + return fixer.removeRange(directive.range); }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; + }, + ], + }); } + return {}; }, }; -export default rule; +type RulesObject = {[name: string]: Rule.RuleModule}; + +export const allRules: RulesObject = LintRules.reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); + +export const recommendedRules: RulesObject = LintRules.filter( + rule => rule.recommended, +).reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 00000000000..e8661d7f96c --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,287 @@ +/** + * 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. + */ + +import {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: Array; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + + constructor(size: number) { + this.#values = new Array(size).fill(SENTINEL); + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773f..a6041bd5cc2 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb3..696261cbf53 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" diff --git a/fixtures/eslint-v9/eslint.config.ts b/fixtures/eslint-v9/eslint.config.ts index 62ef6867163..66d2c087468 100644 --- a/fixtures/eslint-v9/eslint.config.ts +++ b/fixtures/eslint-v9/eslint.config.ts @@ -1,7 +1,9 @@ -import type {Linter} from 'eslint'; -import * as reactHooks from 'eslint-plugin-react-hooks'; +import {defineConfig} from 'eslint/config'; +import reactHooks from 'eslint-plugin-react-hooks'; -export default [ +console.log(reactHooks.configs['recommended-latest']); + +export default defineConfig([ { languageOptions: { ecmaVersion: 'latest', @@ -12,11 +14,12 @@ export default [ }, }, }, - }, - reactHooks.configs['recommended'], - { + plugins: { + 'react-hooks': reactHooks, + }, + extends: ['react-hooks/recommended-latest'], rules: { 'react-hooks/exhaustive-deps': 'error', }, }, -] satisfies Linter.Config[]; +]); diff --git a/fixtures/eslint-v9/package.json b/fixtures/eslint-v9/package.json index 80827a0d173..c09d7ec99bd 100644 --- a/fixtures/eslint-v9/package.json +++ b/fixtures/eslint-v9/package.json @@ -2,7 +2,7 @@ "private": true, "name": "eslint-v9", "dependencies": { - "eslint": "^9.18.0", + "eslint": "^9.33.0", "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks", "jiti": "^2.4.2" }, diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock index 630bf074a30..a471aadd964 100644 --- a/fixtures/eslint-v9/yarn.lock +++ b/fixtures/eslint-v9/yarn.lock @@ -221,26 +221,31 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== dependencies: "@eslint/object-schema" "^2.1.6" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e" - integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg== +"@eslint/config-helpers@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" + integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== + +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846" - integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ== +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -252,22 +257,22 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.21.0": - version "9.21.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.21.0.tgz#4303ef4e07226d87c395b8fad5278763e9c15c08" - integrity sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw== +"@eslint/js@9.33.0": + version "9.33.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368" + integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A== "@eslint/object-schema@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== -"@eslint/plugin-kit@^0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27" - integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g== +"@eslint/plugin-kit@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: - "@eslint/core" "^0.12.0" + "@eslint/core" "^0.15.2" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -350,6 +355,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -475,10 +485,10 @@ escape-string-regexp@^4.0.0: version "0.0.0" uid "" -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -493,18 +503,24 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.18.0: - version "9.21.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.21.0.tgz#b1c9c16f5153ff219791f627b94ab8f11f811591" - integrity sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.33.0: + version "9.33.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.33.0.tgz#cc186b3d9eb0e914539953d6a178a5b413997b73" + integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.2" - "@eslint/core" "^0.12.0" - "@eslint/eslintrc" "^3.3.0" - "@eslint/js" "9.21.0" - "@eslint/plugin-kit" "^0.2.7" + "@eslint/config-array" "^0.21.0" + "@eslint/config-helpers" "^0.3.1" + "@eslint/core" "^0.15.2" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.33.0" + "@eslint/plugin-kit" "^0.3.5" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" @@ -515,9 +531,9 @@ eslint@^9.18.0: cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -533,7 +549,7 @@ eslint@^9.18.0: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1: version "10.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== @@ -542,6 +558,15 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esquery@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" diff --git a/package.json b/package.json index 7d191ee4e28..b73af395bae 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.10.4", "@babel/plugin-transform-object-super": "^7.10.4", "@babel/plugin-transform-parameters": "^7.10.5", + "@babel/plugin-transform-private-methods": "^7.10.4", "@babel/plugin-transform-react-jsx": "^7.23.4", "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.10.5", diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 7a6b09c5440..812c2010a04 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -12,7 +12,8 @@ const ESLintTesterV7 = require('eslint-v7').RuleTester; const ESLintTesterV9 = require('eslint-v9').RuleTester; const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks'); -const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps']; +const ReactHooksESLintRule = + ReactHooksESLintPlugin.default.rules['exhaustive-deps']; /** * A string template tag that removes padding from the left side of multi-line strings diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index ac8886c7768..8d8040bb43c 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -12,7 +12,8 @@ const ESLintTesterV7 = require('eslint-v7').RuleTester; const ESLintTesterV9 = require('eslint-v9').RuleTester; const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks'); -const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['rules-of-hooks']; +const ReactHooksESLintRule = + ReactHooksESLintPlugin.default.rules['rules-of-hooks']; /** * A string template tag that removes padding from the left side of multi-line strings diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index a687d49cc6c..00000000000 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * 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. - */ - -import {ErrorSeverity} from 'babel-plugin-react-compiler'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompiler'; - -const ESLintTesterV8 = require('eslint-v8').RuleTester; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0]?.split('\n') ?? []; - const leftPadding = codeLines[1]?.match(/\s+/)![0] ?? ''; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTesterV8({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts index 9d05cd1871f..d9385bdba43 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,7 +6,7 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompiler'; +import {allRules} from '../src/shared/ReactCompiler'; const ESLintTesterV8 = require('eslint-v8').RuleTester; @@ -74,4 +74,4 @@ const tests: CompilerTestCases = { const eslintTester = new ESLintTesterV8({ parser: require.resolve('@typescript-eslint/parser-v5'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', allRules['immutability'], tests); diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index 26a2e2b2c42..462c3104a70 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -4,63 +4,64 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import type {ESLint, Linter, Rule} from 'eslint'; +import type {Linter, Rule} from 'eslint'; import ExhaustiveDeps from './rules/ExhaustiveDeps'; -import ReactCompiler from './rules/ReactCompiler'; +import {allRules, recommendedRules} from './shared/ReactCompiler'; import RulesOfHooks from './rules/RulesOfHooks'; // All rules const rules = { 'exhaustive-deps': ExhaustiveDeps, - 'react-compiler': ReactCompiler, 'rules-of-hooks': RulesOfHooks, + ...allRules, } satisfies Record; // Config rules -const configRules = { +const ruleConfigs = { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', + ...Object.fromEntries( + Object.keys(recommendedRules).map(name => ['react-hooks/' + name, 'error']), + ), } satisfies Linter.RulesRecord; -// Flat config -const recommendedConfig = { - name: 'react-hooks/recommended', - plugins: { - get 'react-hooks'(): ESLint.Plugin { - return plugin; - }, +const plugin = { + meta: { + name: 'eslint-plugin-react-hooks', }, - rules: configRules, + configs: {}, + rules, }; -// Plugin object -const plugin = { - // TODO: Make this more dynamic to populate version from package.json. - // This can be done by injecting at build time, since importing the package.json isn't an option in Meta - meta: {name: 'eslint-plugin-react-hooks'}, - rules, - configs: { - /** Legacy recommended config, to be used with rc-based configurations */ - 'recommended-legacy': { - plugins: ['react-hooks'], - rules: configRules, +Object.assign(plugin.configs, { + 'recommended-legacy': { + plugins: ['react-hooks'], + rules: ruleConfigs, + }, + + 'flat/recommended': [ + { + plugins: { + 'react-hooks': plugin, + }, + rules: ruleConfigs, }, + ], - /** - * Recommended config, to be used with flat configs. - */ - recommended: recommendedConfig, + 'recommended-latest': [ + { + plugins: { + 'react-hooks': plugin, + }, + rules: ruleConfigs, + }, + ], - /** @deprecated please use `recommended`; will be removed in v7 */ - 'recommended-latest': recommendedConfig, + recommended: { + plugins: ['react-hooks'], + rules: ruleConfigs, }, -} satisfies ESLint.Plugin; - -const configs = plugin.configs; -const meta = plugin.meta; -export {configs, meta, rules}; +}); -// TODO: If the plugin is ever updated to be pure ESM and drops support for rc-based configs, then it should be exporting the plugin as default -// instead of individual named exports. -// export default plugin; +export default plugin; diff --git a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts deleted file mode 100644 index 795a117981a..00000000000 --- a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts +++ /dev/null @@ -1,359 +0,0 @@ -/** - * 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. - */ -/* eslint-disable no-for-of-loops/no-for-of-loops */ - -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-transform-private-methods'; -import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - type CompilerErrorDetail, - type CompilerErrorDetailOptions, - type CompilerDiagnostic, - type CompilerDiagnosticOptions, - CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type Logger, - type LoggerEvent, - type PluginOptions, -} from 'babel-plugin-react-compiler'; -import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; - -function assertExhaustive(_: never, errorMsg: string): never { - throw new Error(errorMsg); -} - -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - -function makeSuggestions( - detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, -): Array { - const suggest: Array = []; - if (Array.isArray(detail.suggestions)) { - for (const suggestion of detail.suggestions) { - switch (suggestion.op) { - case CompilerSuggestionOperation.InsertBefore: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.insertTextBeforeRange( - suggestion.range, - suggestion.text, - ); - }, - }); - break; - case CompilerSuggestionOperation.InsertAfter: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.insertTextAfterRange( - suggestion.range, - suggestion.text, - ); - }, - }); - break; - case CompilerSuggestionOperation.Replace: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.replaceTextRange(suggestion.range, suggestion.text); - }, - }); - break; - case CompilerSuggestionOperation.Remove: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.removeRange(suggestion.range); - }, - }); - break; - default: - assertExhaustive(suggestion, 'Unhandled suggestion operation'); - } - } - } - return suggest; -} - -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; - -const rule: Rule.RuleModule = { - meta: { - type: 'problem', - docs: { - description: 'Surfaces diagnostics from React Forget', - recommended: true, - }, - fixable: 'code', - hasSuggestions: true, - // validation is done at runtime with zod - schema: [{type: 'object', additionalProperties: true}], - }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } - - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } - - const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; - } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; - } - } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; - } - }, -}; - -export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts new file mode 100644 index 00000000000..f006b797817 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts @@ -0,0 +1,216 @@ +/** + * 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. + */ +/* eslint-disable no-for-of-loops/no-for-of-loops */ + +import type {SourceLocation as BabelSourceLocation} from '@babel/types'; +import { + type CompilerDiagnosticOptions, + type CompilerErrorDetailOptions, + CompilerSuggestionOperation, + LintRules, + type LintRule, +} from 'babel-plugin-react-compiler'; +import type {Rule} from 'eslint'; +import runReactCompiler, {RunCacheEntry} from './RunReactCompiler'; + +function assertExhaustive(_: never, errorMsg: string): never { + throw new Error(errorMsg); +} + +function makeSuggestions( + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, +): Array { + const suggest: Array = []; + if (Array.isArray(detail.suggestions)) { + for (const suggestion of detail.suggestions) { + switch (suggestion.op) { + case CompilerSuggestionOperation.InsertBefore: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.insertTextBeforeRange( + suggestion.range, + suggestion.text, + ); + }, + }); + break; + case CompilerSuggestionOperation.InsertAfter: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.insertTextAfterRange( + suggestion.range, + suggestion.text, + ); + }, + }); + break; + case CompilerSuggestionOperation.Replace: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.replaceTextRange(suggestion.range, suggestion.text); + }, + }); + break; + case CompilerSuggestionOperation.Remove: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.removeRange(suggestion.range); + }, + }); + break; + default: + assertExhaustive(suggestion, 'Unhandled suggestion operation'); + } + } + } + return suggest; +} + +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; + + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(rule: LintRule): Rule.RuleModule { + const create = (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.category === rule.category) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + /* + * TODO: if multiple rules report the same linter category, + * we should deduplicate them with a "reported" set + */ + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; + + return { + meta: { + type: 'problem', + docs: { + description: rule.description, + recommended: rule.recommended, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create, + }; +} + +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer): Rule.Fix { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; + +type RulesObject = {[name: string]: Rule.RuleModule}; + +export const allRules: RulesObject = LintRules.reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); + +export const recommendedRules: RulesObject = LintRules.filter( + rule => rule.recommended, +).reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts new file mode 100644 index 00000000000..72e28b1f488 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -0,0 +1,281 @@ +/** + * 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. + */ +/* eslint-disable no-for-of-loops/no-for-of-loops */ + +import {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, + Logger, + LoggerEvent, +} from 'babel-plugin-react-compiler'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: Array; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + + constructor(size: number) { + this.#values = new Array(size).fill(SENTINEL); + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + const idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index ac9ec4fa771..93c02ce8446 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -403,6 +403,7 @@ function getPlugins( // Use Node resolution mechanism. resolve({ // skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0 + preferBuiltins: bundle.preferBuiltins, }), // Remove license headers from individual modules stripBanner({ diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 63e702f77ff..749548f8ec5 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1208,7 +1208,14 @@ const bundles = [ global: 'ESLintPluginReactHooks', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false, - externals: [], + preferBuiltins: true, + externals: [ + '@babel/core', + '@babel/plugin-proposal-private-methods', + 'hermes-parser', + 'zod', + 'zod-validation-error', + ], tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json', prebuild: `mkdir -p ./compiler/packages/babel-plugin-react-compiler/dist && echo "module.exports = require('../src/index.ts');" > ./compiler/packages/babel-plugin-react-compiler/dist/index.js`, }, @@ -1296,9 +1303,21 @@ function getFilename(bundle, bundleType) { } } +let activeBundles = bundles; +if (process.env.BUNDLES_FILTER != null) { + activeBundles = activeBundles.filter( + bundle => bundle.name === process.env.BUNDLES_FILTER + ); + if (activeBundles.length === 0) { + throw new Error( + `No bundles matched for BUNDLES_FILTER=${process.env.BUNDLES_FILTER}` + ); + } +} + module.exports = { bundleTypes, moduleTypes, - bundles, + bundles: activeBundles, getFilename, }; diff --git a/yarn.lock b/yarn.lock index f79e4479cb6..a1ab0f5cc43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,6 +70,15 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -197,6 +206,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" @@ -235,6 +255,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -343,6 +370,19 @@ "@babel/traverse" "^7.26.9" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.27.1": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46" + integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.3" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -449,6 +489,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + "@babel/helper-hoist-variables@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" @@ -478,6 +523,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-member-expression-to-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" + integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-module-imports@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -561,6 +614,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" @@ -581,6 +641,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + "@babel/helper-plugin-utils@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" @@ -644,6 +709,15 @@ "@babel/helper-optimise-call-expression" "^7.25.9" "@babel/traverse" "^7.26.5" +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/helper-simple-access@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" @@ -681,6 +755,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" @@ -707,6 +789,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.14.0", "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" @@ -722,6 +809,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" @@ -849,6 +941,13 @@ dependencies: "@babel/types" "^7.26.10" +"@babel/parser@^7.27.2", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" @@ -1765,6 +1864,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/plugin-transform-private-methods@^7.10.4": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-private-methods@^7.24.4", "@babel/plugin-transform-private-methods@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" @@ -2366,6 +2473,15 @@ "@babel/parser" "^7.26.9" "@babel/types" "^7.26.9" +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -2446,6 +2562,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.13", "@babel/types@^7.12.5", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" @@ -2462,6 +2591,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3047,6 +3184,14 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.8" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" @@ -3119,6 +3264,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -8100,7 +8253,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" -"eslint-v7@npm:eslint@^7.7.0": +"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -8299,52 +8452,6 @@ eslint@8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" -eslint@^7.7.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - espree@10.0.1, espree@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" @@ -15960,7 +16067,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15995,15 +16102,6 @@ string-width@^4.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16064,7 +16162,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16092,13 +16190,6 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -17681,7 +17772,7 @@ workerize-loader@^2.0.2: dependencies: loader-utils "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17699,15 +17790,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From a85ec041d63428d02b952f93822e429322cfe821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 21 Aug 2025 18:03:05 -0400 Subject: [PATCH 4/5] [DevTools] Ignore List Stack Traces (#34210) Co-authored-by: Sebastian Sebbie Silbermann --- fixtures/flight/config/webpack.config.js | 10 ++++++ fixtures/flight/package.json | 1 + fixtures/flight/server/region.js | 3 +- fixtures/flight/yarn.lock | 36 +++++-------------- .../src/__tests__/utils-test.js | 5 ++- .../views/Components/InspectedElement.js | 6 ++-- .../Components/InspectedElementSourcePanel.js | 9 ++--- .../views/Components/InspectedElementView.js | 4 +-- .../InspectedElementViewSourceButton.js | 7 ++-- .../views/Components/OpenInEditorButton.js | 5 +-- .../views/Components/StackTraceView.css | 6 +++- .../views/Components/StackTraceView.js | 24 ++++++++----- .../src/hooks/SourceMapConsumer.js | 6 +++- .../src/hooks/SourceMapTypes.js | 1 + .../src/symbolicateSource.js | 27 ++++++++++---- 15 files changed, 89 insertions(+), 61 deletions(-) diff --git a/fixtures/flight/config/webpack.config.js b/fixtures/flight/config/webpack.config.js index 69be1859fdc..2aeede652b5 100644 --- a/fixtures/flight/config/webpack.config.js +++ b/fixtures/flight/config/webpack.config.js @@ -15,6 +15,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const DevToolsIgnorePlugin = require('devtools-ignore-webpack-plugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); const modules = require('./modules'); @@ -685,6 +686,15 @@ module.exports = function (webpackEnv) { }, }), // Fork Start + new DevToolsIgnorePlugin({ + shouldIgnorePath: function (path) { + return ( + path.includes('/node_modules/') || + path.includes('/webpack/') || + path.endsWith('/src/index.js') + ); + }, + }), new ReactFlightWebpackPlugin({ isServer: false, clientReferences: { diff --git a/fixtures/flight/package.json b/fixtures/flight/package.json index 2aa38c85cab..6018ffb0e4f 100644 --- a/fixtures/flight/package.json +++ b/fixtures/flight/package.json @@ -29,6 +29,7 @@ "concurrently": "^7.3.0", "css-loader": "^6.5.1", "css-minimizer-webpack-plugin": "^3.2.0", + "devtools-ignore-webpack-plugin": "^0.2.0", "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", "file-loader": "^6.2.0", diff --git a/fixtures/flight/server/region.js b/fixtures/flight/server/region.js index 564c7b78dd9..0f96f3aa74b 100644 --- a/fixtures/flight/server/region.js +++ b/fixtures/flight/server/region.js @@ -58,7 +58,8 @@ function filterStackFrame(sourceURL, functionName) { sourceURL !== '' && !sourceURL.startsWith('node:') && !sourceURL.includes('node_modules') && - !sourceURL.endsWith('library.js') + !sourceURL.endsWith('library.js') && + !sourceURL.includes('/server/region.js') ); } diff --git a/fixtures/flight/yarn.lock b/fixtures/flight/yarn.lock index 928e18a35eb..3acf46a0a00 100644 --- a/fixtures/flight/yarn.lock +++ b/fixtures/flight/yarn.lock @@ -4614,6 +4614,11 @@ detect-port-alt@^1.1.6: address "^1.0.1" debug "^2.6.0" +devtools-ignore-webpack-plugin@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/devtools-ignore-webpack-plugin/-/devtools-ignore-webpack-plugin-0.2.0.tgz#a7b3d1bd0f593c7fee5cbb7534b07860e5e2447c" + integrity sha512-4P+1Y1VhSt1MRBRF6my8N1bs9nNMOFfIFSBHI6u18W73iCHWXNHTSWYeMoQQ72PIIHrP1q4koKpYg1Em3jb9Rw== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -8650,16 +8655,7 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8730,14 +8726,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9452,16 +9441,7 @@ wordwrap@~1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== diff --git a/packages/react-devtools-shared/src/__tests__/utils-test.js b/packages/react-devtools-shared/src/__tests__/utils-test.js index dacbe0f46b9..dcffd2a228b 100644 --- a/packages/react-devtools-shared/src/__tests__/utils-test.js +++ b/packages/react-devtools-shared/src/__tests__/utils-test.js @@ -401,7 +401,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.f = f; function f() { } //# sourceMappingURL=`; - const result = ['', 'http://test/a.mts', 1, 17]; + const result = { + location: ['', 'http://test/a.mts', 1, 17], + ignored: false, + }; const fs = { 'http://test/a.mts': `export function f() {}`, 'http://test/a.mjs.map': `{"version":3,"file":"a.mjs","sourceRoot":"","sources":["a.mts"],"names":[],"mappings":";;AAAA,cAAsB;AAAtB,SAAgB,CAAC,KAAI,CAAC"}`, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index e7b70520572..200f78586c5 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -7,6 +7,8 @@ * @flow */ +import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource'; + import * as React from 'react'; import {useCallback, useContext, useSyncExternalStore} from 'react'; import {TreeStateContext} from './TreeContext'; @@ -28,8 +30,6 @@ import useEditorURL from '../useEditorURL'; import styles from './InspectedElement.css'; -import type {ReactFunctionLocation} from 'shared/ReactTypes'; - export type Props = {}; // TODO Make edits and deletes also use transition API! @@ -61,7 +61,7 @@ export default function InspectedElementWrapper(_: Props): React.Node { ? inspectedElement.stack[0] : null; - const symbolicatedSourcePromise: Promise = + const symbolicatedSourcePromise: Promise = React.useMemo(() => { if (fetchFileWithCaching == null) return noSourcePromise; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js index a3dea3f888f..9faf6c1d7ab 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js @@ -17,6 +17,7 @@ import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/wit import useOpenResource from '../useOpenResource'; +import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; import styles from './InspectedElementSourcePanel.css'; @@ -24,7 +25,7 @@ import formatLocationForDisplay from './formatLocationForDisplay'; type Props = { source: ReactFunctionLocation, - symbolicatedSourcePromise: Promise, + symbolicatedSourcePromise: Promise, }; function InspectedElementSourcePanel({ @@ -80,7 +81,7 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) { ); } - const [, sourceURL, line, column] = symbolicatedSource; + const [, sourceURL, line, column] = symbolicatedSource.location; const handleCopy = withPermissionsCheck( {permissions: ['clipboardWrite']}, () => copy(`${sourceURL}:${line}:${column}`), @@ -98,11 +99,11 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) { const [linkIsEnabled, viewSource] = useOpenResource( source, - symbolicatedSource, + symbolicatedSource == null ? null : symbolicatedSource.location, ); const [, sourceURL, line, column] = - symbolicatedSource == null ? source : symbolicatedSource; + symbolicatedSource == null ? source : symbolicatedSource.location; return (
, + symbolicatedSourcePromise: Promise, }; export default function InspectedElementView({ diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js index ee2fbe6c4d2..7170c2494f9 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js @@ -13,12 +13,13 @@ import ButtonIcon from '../ButtonIcon'; import Button from '../Button'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; +import type {SourceMappedLocation} from 'react-devtools-shared/src/symbolicateSource'; import useOpenResource from '../useOpenResource'; type Props = { source: null | ReactFunctionLocation, - symbolicatedSourcePromise: Promise | null, + symbolicatedSourcePromise: Promise | null, }; function InspectedElementViewSourceButton({ @@ -42,7 +43,7 @@ function InspectedElementViewSourceButton({ type ActualSourceButtonProps = { source: null | ReactFunctionLocation, - symbolicatedSourcePromise: Promise | null, + symbolicatedSourcePromise: Promise | null, }; function ActualSourceButton({ source, @@ -55,7 +56,7 @@ function ActualSourceButton({ const [buttonIsEnabled, viewSource] = useOpenResource( source, - symbolicatedSource, + symbolicatedSource == null ? null : symbolicatedSource.location, ); return (