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 d3e763a8c4b9c..7d191ee4e28ad 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 22479aac04471..f75f54f4ead0f 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 4d1e60e53a647..76f59b1fc541d 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 4766c59b245f9..5ad58533624c1 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 05ee9df9adfd7..b8a8749c10277 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 48560ae65350d..da7e1c0a00bbc 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 baa297cf33d41..707a68a117e5c 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 c06e52a578ddb..707a68a117e5c 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 bc9c259b3922f..b8b3af14d5993 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 c0a406642bd11..f79e4479cb62a 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 2eef555d36f82..0e22e3f294fb5 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 0fd9b869c6141..01f37319bf574 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 45efe2a870820..02ffb447b3719 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 2055b25c0c943..5940dbb4fd6ba 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 24ce37cf72c29..9653c49576a64 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 79bbee37a565c..92151501622bf 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 ee341b111f1ae..a0d06f96f0e52 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 16d7c3713c6dd..beaaff0f79a33 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 3b11670146b60..77f2a04e7cf67 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 81959ea361564..272b5fc0752d6 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 34ce7f7694b79..7d2b0b234c7f4 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 80694a7a78412..7f30e25a5c060 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 7aeb3edb22a11..412efcfe7aede 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 d051fb37250a1..36f32213eab79 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 91e2ce0692576..3588cf32f9f66 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 f7da5229548ab..c02a41f8f0aaf 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 f88c85f2f0f39..393f95d94c46b 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 85fb3922d66a6..32e3ec82c5925 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 b28228339ce5f..af596155253cd 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 163d645957c05..e1ed71049dff3 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 b33cfb1512349..186641c3f20d4 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 8989cb1ac2d62..d0cf41a13c4fb 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 d026a94ed4a29..f1fa5aec4071a 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 f3543e113a17f..f49a9a0a47560 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 e7f8b1c0e5b11..868352e050e7e 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 eea6c0a08e951..158935f9e6ed9 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 e1c17625f4895..679d355e3215b 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 9c4efa380cd40..4a24098a9fe7c 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 81209d61c6853..2ee9ab64b7ee4 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 6bb59247dab7c..516ca232e9e4b 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 7f5fb408b4596..375b8e8f0116f 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 69ab401c89f8a..3146bbea38a61 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 fa9e20a418e3e..c4adc4c9bfbd9 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 3d54bcd75c577..3a5990e1624af 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 c892066bf890a..c2beb3424a162 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 a617a2f572ee8..faef4dd599566 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 dfb5c7b56fdf4..9d9c741f7aed4 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 f03b514c3fe4d..07dd0b0a17e76 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 42d988804b77e..58e85caf84f27 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 d8250c6f57376..7f8f1d4331060 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 d3a6e5f0d712a..14bd646bb152f 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 d3b746b1f8ade..2f930964acd43 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 1ecf4778d79ae..9a8a2a78e06e4 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 08eb396bb1a30..7ecd65382cc6e 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 6583f0a4e2dcf..5c7c4360e7cff 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 b6e9e87ada427..88d4849b4f673 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 b629bfef27369..9107c896cc01c 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 c16890e52e5fc..cb5203c88165a 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 5021e7978d91b..6a9b4d98e28f5 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 7bcc917447a26..00473c8eec7ca 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 dd275b857f32d..825bc4a1df05d 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 2e0c890367e4a..7d490de8c6ae3 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 1224a5b9cf9ca..c0cac509c8854 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 7bec7f73b551a..337997caa594d 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 e7f9ad7f30fd6..019beccdc86f5 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 f6220645ef87b..f673b27779859 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 4a72cdd855516..a44cf6a9f0e0c 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 34244892756b4..2830d70d95c8d 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 0000000000000..a745397078aef --- /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 0000000000000..0ba165011c6b0 --- /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 0000000000000..9a09f4c4e0537 --- /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 0000000000000..c361670e97416 --- /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 0000000000000..9042980a807b9 --- /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 0000000000000..77f6dd93fbf87 --- /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 0000000000000..6efd069aafa07 --- /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 bff40e9649d96..0000000000000 --- 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 5a2bea68526fb..87baf724e121d 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 0000000000000..2ab24581d8d07 --- /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 2dd191f033dcf..e5402611e2bbb 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 a3577a101ef43..1a339e8331009 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 730b6ff6f85ae..d2e8fdd42e0c3 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 0000000000000..e8661d7f96c0b --- /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 a159359773f31..a6041bd5cc2a7 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 6e1bc7feeb32f..696261cbf53af 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 62ef68671639e..66d2c087468b2 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 80827a0d1730a..c09d7ec99bd8a 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 630bf074a30d0..a471aadd964cf 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 7d191ee4e28ad..b73af395bae25 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 7a6b09c5440c4..812c2010a042d 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 ac8886c776802..8d8040bb43cd1 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 a687d49cc6c8b..0000000000000 --- 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 9d05cd1871fe9..d9385bdba4335 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 26a2e2b2c4276..462c3104a70a2 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 795a117981ab8..0000000000000 --- 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 0000000000000..f006b79781755 --- /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 0000000000000..72e28b1f488ee --- /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 ac9ec4fa7714e..93c02ce844649 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 63e702f77ff54..749548f8ec51f 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 f79e4479cb62a..a1ab0f5cc4386 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 69be1859fdc6c..2aeede652b573 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 2aa38c85cabb1..6018ffb0e4ff5 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 564c7b78dd9d0..0f96f3aa74b12 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 928e18a35eb8e..3acf46a0a0080 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 dacbe0f46b9a6..dcffd2a228b0c 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 e7b7052057257..200f78586c51a 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 a3dea3f888f58..9faf6c1d7abcc 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 ee2fbe6c4d209..7170c2494f9c2 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 (