diff --git a/compiler/apps/playground/components/AccordionWindow.tsx b/compiler/apps/playground/components/AccordionWindow.tsx index 09614bd505..311bf2590d 100644 --- a/compiler/apps/playground/components/AccordionWindow.tsx +++ b/compiler/apps/playground/components/AccordionWindow.tsx @@ -7,7 +7,6 @@ import {Resizable} from 're-resizable'; import React, { - useCallback, useId, unstable_ViewTransition as ViewTransition, unstable_addTransitionType as addTransitionType, @@ -66,7 +65,7 @@ function AccordionWindowItem({ const transitionName = `accordion-window-item-${id}`; - const toggleTabs = () => { + const toggleTabs = (): void => { startTransition(() => { addTransitionType(EXPAND_ACCORDION_TRANSITION); const nextState = new Set(tabsOpen); diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx index d922f27c97..18f904d225 100644 --- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx +++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx @@ -6,7 +6,6 @@ */ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react'; -import {PluginOptions} from 'babel-plugin-react-compiler'; import type {editor} from 'monaco-editor'; import * as monaco from 'monaco-editor'; import React, { @@ -18,9 +17,8 @@ import React, { } from 'react'; import {Resizable} from 're-resizable'; import {useStore, useStoreDispatch} from '../StoreContext'; -import {monacoOptions} from './monacoOptions'; +import {monacoConfigOptions} from './monacoOptions'; import {IconChevron} from '../Icons/IconChevron'; -import prettyFormat from 'pretty-format'; import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes'; // @ts-expect-error - webpack asset/source loader handles .d.ts files as strings @@ -29,9 +27,9 @@ import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts'; loader.config({monaco}); export default function ConfigEditor({ - appliedOptions, + formattedAppliedConfig, }: { - appliedOptions: PluginOptions | null; + formattedAppliedConfig: string; }): React.ReactElement { const [isExpanded, setIsExpanded] = useState(false); @@ -49,7 +47,7 @@ export default function ConfigEditor({ setIsExpanded(false); }); }} - appliedOptions={appliedOptions} + formattedAppliedConfig={formattedAppliedConfig} />
void; - appliedOptions: PluginOptions | null; + onToggle: (expanded: boolean) => void; + formattedAppliedConfig: string; }): React.ReactElement { const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -122,13 +120,6 @@ function ExpandedEditor({ }); }; - const formattedAppliedOptions = appliedOptions - ? prettyFormat(appliedOptions, { - printFunctionName: false, - printBasicPrototype: false, - }) - : 'Invalid configs'; - return ( @@ -158,7 +149,7 @@ function ExpandedEditor({ Config Overrides
-
+
@@ -186,23 +168,16 @@ function ExpandedEditor({ Applied Configs -
+
diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index e7068534cd..5b39b91654 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,316 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse, ParseResult} from '@babel/parser'; -import * as HermesParser from 'hermes-parser'; -import * as t from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerError, +import { CompilerErrorDetail, CompilerDiagnostic, - Effect, - ErrorCategory, - parseConfigPragmaForTests, - ValueKind, - type Hook, - PluginOptions, - CompilerPipelineValue, - parsePluginOptions, - printReactiveFunctionWithOutlined, - printFunctionWithOutlined, - type LoggerEvent, } from 'babel-plugin-react-compiler'; -import { - useDeferredValue, - useMemo, - unstable_ViewTransition as ViewTransition, -} from 'react'; +import {useDeferredValue, useMemo, useState} from 'react'; import {useStore} from '../StoreContext'; import ConfigEditor from './ConfigEditor'; import Input from './Input'; -import { - CompilerOutput, - CompilerTransformOutput, - default as Output, - PrintedCompilerPipelineValue, -} from './Output'; -import {transformFromAstSync} from '@babel/core'; - -function parseInput( - input: string, - language: 'flow' | 'typescript', -): ParseResult { - // Extract the first line to quickly check for custom test directives - if (language === 'flow') { - return HermesParser.parse(input, { - babel: true, - flow: 'all', - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }); - } else { - return babelParse(input, { - plugins: ['typescript', 'jsx'], - sourceType: 'module', - }) as ParseResult; - } -} - -function invokeCompiler( - source: string, - language: 'flow' | 'typescript', - options: PluginOptions, -): CompilerTransformOutput { - const ast = parseInput(source, language); - let result = transformFromAstSync(ast, source, { - filename: '_playgroundFile.js', - highlightCode: false, - retainLines: true, - plugins: [[BabelPluginReactCompiler, options]], - ast: true, - sourceType: 'module', - configFile: false, - sourceMaps: true, - babelrc: false, - }); - if (result?.ast == null || result?.code == null || result?.map == null) { - throw new Error('Expected successful compilation'); - } - return { - code: result.code, - sourceMaps: result.map, - language, - }; -} - -const COMMON_HOOKS: Array<[string, Hook]> = [ - [ - 'useFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePaginationFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useRefetchableFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useLazyLoadQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePreloadedQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], -]; - -function parseOptions( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): PluginOptions { - // Extract the first line to quickly check for custom test directives - const pragma = source.substring(0, source.indexOf('\n')); - - const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { - compilationMode: 'infer', - environment: - mode === 'linter' - ? { - // enabled in compiler - validateRefAccessDuringRender: false, - // enabled in linter - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - } - : { - /* use defaults for compiler mode */ - }, - }); - - // Parse config overrides from config editor - let configOverrideOptions: any = {}; - const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); - if (configOverrides.trim()) { - if (configMatch && configMatch[1]) { - const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); - configOverrideOptions = new Function(`return (${configString})`)(); - } else { - throw new Error('Invalid override format'); - } - } - - const opts: PluginOptions = parsePluginOptions({ - ...parsedPragmaOptions, - ...configOverrideOptions, - environment: { - ...parsedPragmaOptions.environment, - ...configOverrideOptions.environment, - customHooks: new Map([...COMMON_HOOKS]), - }, - }); - - return opts; -} - -function compile( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { - const results = new Map>(); - const error = new CompilerError(); - const otherErrors: Array = []; - const upsert: (result: PrintedCompilerPipelineValue) => void = result => { - const entry = results.get(result.name); - if (Array.isArray(entry)) { - entry.push(result); - } else { - results.set(result.name, [result]); - } - }; - let language: 'flow' | 'typescript'; - if (source.match(/\@flow/)) { - language = 'flow'; - } else { - language = 'typescript'; - } - let transformOutput; - - let baseOpts: PluginOptions | null = null; - try { - baseOpts = parseOptions(source, mode, configOverrides); - } catch (err) { - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Config, - reason: `Unexpected failure when transforming configs! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - if (baseOpts) { - try { - const logIR = (result: CompilerPipelineValue): void => { - switch (result.kind) { - case 'ast': { - break; - } - case 'hir': { - upsert({ - kind: 'hir', - fnName: result.value.id, - name: result.name, - value: printFunctionWithOutlined(result.value), - }); - break; - } - case 'reactive': { - upsert({ - kind: 'reactive', - fnName: result.value.id, - name: result.name, - value: printReactiveFunctionWithOutlined(result.value), - }); - break; - } - case 'debug': { - upsert({ - kind: 'debug', - fnName: null, - name: result.name, - value: result.value, - }); - break; - } - default: { - const _: never = result; - throw new Error(`Unhandled result ${result}`); - } - } - }; - // Add logger options to the parsed options - const opts = { - ...baseOpts, - logger: { - debugLogIRs: logIR, - logEvent: (_filename: string | null, event: LoggerEvent): void => { - if (event.kind === 'CompileError') { - otherErrors.push(event.detail); - } - }, - }, - }; - transformOutput = invokeCompiler(source, language, opts); - } catch (err) { - /** - * error might be an invariant violation or other runtime error - * (i.e. object shape that is not CompilerError) - */ - if (err instanceof CompilerError && err.details.length > 0) { - error.merge(err); - } else { - /** - * Handle unexpected failures by logging (to get a stack trace) - * and reporting - */ - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Invariant, - reason: `Unexpected failure when transforming input! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - } - } - // Only include logger errors if there weren't other errors - if (!error.hasErrors() && otherErrors.length !== 0) { - otherErrors.forEach(e => error.details.push(e)); - } - if (error.hasErrors()) { - return [{kind: 'err', results, error}, language, baseOpts]; - } - return [ - {kind: 'ok', results, transformOutput, errors: error.details}, - language, - baseOpts, - ]; -} +import {CompilerOutput, default as Output} from './Output'; +import {compile} from '../../lib/compilation'; +import prettyFormat from 'pretty-format'; export default function Editor(): JSX.Element { const store = useStore(); @@ -327,6 +28,7 @@ export default function Editor(): JSX.Element { () => compile(deferredStore.source, 'linter', deferredStore.config), [deferredStore.source, deferredStore.config], ); + const [formattedAppliedConfig, setFormattedAppliedConfig] = useState(''); let mergedOutput: CompilerOutput; let errors: Array; @@ -340,11 +42,22 @@ export default function Editor(): JSX.Element { mergedOutput = compilerOutput; errors = compilerOutput.error.details; } + + if (appliedOptions) { + const formatted = prettyFormat(appliedOptions, { + printFunctionName: false, + printBasicPrototype: false, + }); + if (formatted !== formattedAppliedConfig) { + setFormattedAppliedConfig(formatted); + } + } + return ( <>
- +
diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 17da512e80..8c22ca35fc 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -372,12 +372,18 @@ function TextTabContent({ loading={''} options={{ ...monacoOptions, + scrollbar: { + vertical: 'hidden', + }, + dimension: { + width: 0, + height: 0, + }, readOnly: true, lineNumbers: 'off', glyphMargin: false, // Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882 - lineDecorationsWidth: 0, - lineNumbersMinChars: 0, + overviewRulerLanes: 0, }} /> ) : ( diff --git a/compiler/apps/playground/components/Editor/monacoOptions.ts b/compiler/apps/playground/components/Editor/monacoOptions.ts index 7fed1b7875..d52c8bbedf 100644 --- a/compiler/apps/playground/components/Editor/monacoOptions.ts +++ b/compiler/apps/playground/components/Editor/monacoOptions.ts @@ -32,3 +32,14 @@ export const monacoOptions: Partial = { tabSize: 2, }; + +export const monacoConfigOptions: Partial = { + ...monacoOptions, + lineNumbers: 'off', + renderLineHighlight: 'none', + overviewRulerBorder: false, + overviewRulerLanes: 0, + fontSize: 12, + scrollBeyondLastLine: false, + glyphMargin: false, +}; diff --git a/compiler/apps/playground/lib/compilation.ts b/compiler/apps/playground/lib/compilation.ts new file mode 100644 index 0000000000..10bf0164c0 --- /dev/null +++ b/compiler/apps/playground/lib/compilation.ts @@ -0,0 +1,308 @@ +/** + * 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 {parse as babelParse, ParseResult} from '@babel/parser'; +import * as HermesParser from 'hermes-parser'; +import * as t from '@babel/types'; +import BabelPluginReactCompiler, { + CompilerError, + CompilerErrorDetail, + CompilerDiagnostic, + Effect, + ErrorCategory, + parseConfigPragmaForTests, + ValueKind, + type Hook, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, + printReactiveFunctionWithOutlined, + printFunctionWithOutlined, + type LoggerEvent, +} from 'babel-plugin-react-compiler'; +import {transformFromAstSync} from '@babel/core'; +import type { + CompilerOutput, + CompilerTransformOutput, + PrintedCompilerPipelineValue, +} from '../components/Editor/Output'; + +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { + // Extract the first line to quickly check for custom test directives + if (language === 'flow') { + return HermesParser.parse(input, { + babel: true, + flow: 'all', + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }); + } else { + return babelParse(input, { + plugins: ['typescript', 'jsx'], + sourceType: 'module', + }) as ParseResult; + } +} + +function invokeCompiler( + source: string, + language: 'flow' | 'typescript', + options: PluginOptions, +): CompilerTransformOutput { + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, options]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); + } + return { + code: result.code, + sourceMaps: result.map, + language, + }; +} + +const COMMON_HOOKS: Array<[string, Hook]> = [ + [ + 'useFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePaginationFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useRefetchableFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useLazyLoadQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePreloadedQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], +]; + +function parseOptions( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): PluginOptions { + // Extract the first line to quickly check for custom test directives + const pragma = source.substring(0, source.indexOf('\n')); + + const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { + compilationMode: 'infer', + environment: + mode === 'linter' + ? { + // enabled in compiler + validateRefAccessDuringRender: false, + // enabled in linter + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + } + : { + /* use defaults for compiler mode */ + }, + }); + + // Parse config overrides from config editor + let configOverrideOptions: any = {}; + const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); + if (configOverrides.trim()) { + if (configMatch && configMatch[1]) { + const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); + configOverrideOptions = new Function(`return (${configString})`)(); + } else { + throw new Error('Invalid override format'); + } + } + + const opts: PluginOptions = parsePluginOptions({ + ...parsedPragmaOptions, + ...configOverrideOptions, + environment: { + ...parsedPragmaOptions.environment, + ...configOverrideOptions.environment, + customHooks: new Map([...COMMON_HOOKS]), + }, + }); + + return opts; +} + +export function compile( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { + const results = new Map>(); + const error = new CompilerError(); + const otherErrors: Array = []; + const upsert: (result: PrintedCompilerPipelineValue) => void = result => { + const entry = results.get(result.name); + if (Array.isArray(entry)) { + entry.push(result); + } else { + results.set(result.name, [result]); + } + }; + let language: 'flow' | 'typescript'; + if (source.match(/\@flow/)) { + language = 'flow'; + } else { + language = 'typescript'; + } + let transformOutput; + + let baseOpts: PluginOptions | null = null; + try { + baseOpts = parseOptions(source, mode, configOverrides); + } catch (err) { + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Config, + reason: `Unexpected failure when transforming configs! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + if (baseOpts) { + try { + const logIR = (result: CompilerPipelineValue): void => { + switch (result.kind) { + case 'ast': { + break; + } + case 'hir': { + upsert({ + kind: 'hir', + fnName: result.value.id, + name: result.name, + value: printFunctionWithOutlined(result.value), + }); + break; + } + case 'reactive': { + upsert({ + kind: 'reactive', + fnName: result.value.id, + name: result.name, + value: printReactiveFunctionWithOutlined(result.value), + }); + break; + } + case 'debug': { + upsert({ + kind: 'debug', + fnName: null, + name: result.name, + value: result.value, + }); + break; + } + default: { + const _: never = result; + throw new Error(`Unhandled result ${result}`); + } + } + }; + // Add logger options to the parsed options + const opts = { + ...baseOpts, + logger: { + debugLogIRs: logIR, + logEvent: (_filename: string | null, event: LoggerEvent): void => { + if (event.kind === 'CompileError') { + otherErrors.push(event.detail); + } + }, + }, + }; + transformOutput = invokeCompiler(source, language, opts); + } catch (err) { + /** + * error might be an invariant violation or other runtime error + * (i.e. object shape that is not CompilerError) + */ + if (err instanceof CompilerError && err.details.length > 0) { + error.merge(err); + } else { + /** + * Handle unexpected failures by logging (to get a stack trace) + * and reporting + */ + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Invariant, + reason: `Unexpected failure when transforming input! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + } + } + // Only include logger errors if there weren't other errors + if (!error.hasErrors() && otherErrors.length !== 0) { + otherErrors.forEach(e => error.details.push(e)); + } + if (error.hasErrors()) { + return [{kind: 'err', results, error}, language, baseOpts]; + } + return [ + {kind: 'ok', results, transformOutput, errors: error.details}, + language, + baseOpts, + ]; +} diff --git a/compiler/apps/playground/styles/globals.css b/compiler/apps/playground/styles/globals.css index 55f03c46d2..86b069f057 100644 --- a/compiler/apps/playground/styles/globals.css +++ b/compiler/apps/playground/styles/globals.css @@ -124,3 +124,11 @@ ::view-transition-group(.expand-accordion) { overflow: clip; } + +/** + * For some reason, the original Monaco editor is still visible to the + * left of the DiffEditor. This is a workaround for better visual clarity. + */ +.monaco-diff-editor .editor.original{ + visibility: hidden !important; +} diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts deleted file mode 100644 index 77f6dd93fb..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts +++ /dev/null @@ -1,58 +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 {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/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 3eb6dfa0c7..7dacbe6d19 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -161,69 +161,21 @@ function makeRule(rule: LintRule): Rule.RuleModule { }; } -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 RulesConfig = { [name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity}; }; -export const allRules: RulesConfig = LintRules.reduce( - (acc, rule) => { - acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; - return acc; - }, - { - 'no-unused-directives': { - rule: NoUnusedDirectivesRule, - severity: ErrorSeverity.Error, - }, - } as RulesConfig, -); +export const allRules: RulesConfig = LintRules.reduce((acc, rule) => { + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; + return acc; +}, {} as RulesConfig); export const recommendedRules: RulesConfig = LintRules.filter( rule => rule.recommended, -).reduce( - (acc, rule) => { - acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; - return acc; - }, - { - 'no-unused-directives': { - rule: NoUnusedDirectivesRule, - severity: ErrorSeverity.Error, - }, - } as RulesConfig, -); +).reduce((acc, rule) => { + acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; + return acc; +}, {} as RulesConfig); export function mapErrorSeverityToESlint( severity: ErrorSeverity, diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts index f11d0b910d..53d2f31e0f 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -5,20 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync, traverse} from '@babel/core'; +import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; -import {Directive, File} from '@babel/types'; +import {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'; @@ -45,17 +43,11 @@ const COMPILER_OPTIONS: PluginOptions = { }), }; -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; }; @@ -87,25 +79,6 @@ function getFlowSuppressions( 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, @@ -125,7 +98,6 @@ function runReactCompilerImpl({ filename, userOpts, flowSuppressions: [], - unusedOptOutDirectives: [], events: [], }; const userLogger: Logger | null = options.logger; @@ -181,29 +153,6 @@ function runReactCompilerImpl({ 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 */ } diff --git a/fixtures/eslint-v6/.eslintrc.json b/fixtures/eslint-v6/.eslintrc.json index 33b2cfaaca..f76a20fea5 100644 --- a/fixtures/eslint-v6/.eslintrc.json +++ b/fixtures/eslint-v6/.eslintrc.json @@ -1,14 +1,11 @@ { "root": true, - "extends": ["plugin:react-hooks/recommended-legacy"], + "extends": ["plugin:react-hooks/recommended-latest-legacy"], "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } - }, - "rules": { - "react-hooks/exhaustive-deps": "error" } } diff --git a/fixtures/eslint-v6/index.js b/fixtures/eslint-v6/index.js index 6d4a7235dd..23af473216 100644 --- a/fixtures/eslint-v6/index.js +++ b/fixtures/eslint-v6/index.js @@ -141,3 +141,27 @@ function useHookInLoops() { useHook4(); } } + +/** + * Compiler Rules + */ +// Invalid: component factory +function InvalidComponentFactory() { + const DynamicComponent = () =>
Hello
; + // eslint-disable-next-line react-hooks/static-components + return ; +} + +// Invalid: mutating globals +function InvalidGlobals() { + // eslint-disable-next-line react-hooks/immutability + window.myGlobal = 42; + return
Done
; +} + +// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization +function InvalidUseMemo({items}) { + // eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps + const sorted = useMemo(() => [...items].sort(), []); + return
{sorted.length}
; +} diff --git a/fixtures/eslint-v6/yarn.lock b/fixtures/eslint-v6/yarn.lock index e8e670f47a..5af9623e87 100644 --- a/fixtures/eslint-v6/yarn.lock +++ b/fixtures/eslint-v6/yarn.lock @@ -2,15 +2,7 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.2": +"@babel/code-frame@^7.0.0": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -19,228 +11,11 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/compat-data@^7.26.5": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" - integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== - -"@babel/core@^7.24.4": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" - integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/traverse" "^7.26.10" - "@babel/types" "^7.26.10" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" - integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== - dependencies: - "@babel/parser" "^7.26.10" - "@babel/types" "^7.26.10" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" - integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== - dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" - integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.26.9" - semver "^6.3.1" - -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-plugin-utils@^7.25.9": - version "7.26.5" - 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-replace-supers@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - 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-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helpers@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" - integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== - dependencies: - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - -"@babel/parser@^7.24.4", "@babel/parser@^7.26.10", "@babel/parser@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== - dependencies: - "@babel/types" "^7.26.10" - -"@babel/plugin-transform-private-methods@^7.24.4": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/template@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" - integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.26.9" - "@babel/types" "^7.26.9" - -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" - integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" - integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - acorn-jsx@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -317,26 +92,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -browserslist@^4.24.0: - version "4.24.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" - integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== - dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001688: - version "1.0.30001703" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718" - integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ== - chalk@^2.1.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -400,11 +160,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - cross-spawn@^6.0.5: version "6.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" @@ -416,7 +171,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -debug@^4.0.1, debug@^4.1.0, debug@^4.3.1: +debug@^4.0.1: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -435,11 +190,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.5.73: - version "1.5.114" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz#f2bb4fda80a7db4ea273565e75b0ebbe19af0ac3" - integrity sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA== - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -450,11 +200,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -632,11 +377,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - glob-parent@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -656,11 +396,6 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^12.1.0: version "12.4.0" resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" @@ -678,18 +413,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -787,11 +510,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -802,11 +520,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -820,13 +533,6 @@ lodash@^4.17.14, lodash@^4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -871,11 +577,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -924,7 +625,7 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== -picocolors@^1.0.0, picocolors@^1.1.1: +picocolors@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -991,7 +692,7 @@ semver@^5.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.1.2, semver@^6.3.1: +semver@^6.1.2: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -1127,14 +828,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -update-browserslist-db@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -1170,18 +863,3 @@ write@1.0.3: integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== dependencies: mkdirp "^0.5.1" - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -zod-validation-error@^3.0.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6" - integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== - -zod@^3.22.4: - version "3.24.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" - integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== diff --git a/fixtures/eslint-v7/.eslintrc.json b/fixtures/eslint-v7/.eslintrc.json index 33b2cfaaca..f76a20fea5 100644 --- a/fixtures/eslint-v7/.eslintrc.json +++ b/fixtures/eslint-v7/.eslintrc.json @@ -1,14 +1,11 @@ { "root": true, - "extends": ["plugin:react-hooks/recommended-legacy"], + "extends": ["plugin:react-hooks/recommended-latest-legacy"], "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } - }, - "rules": { - "react-hooks/exhaustive-deps": "error" } } diff --git a/fixtures/eslint-v7/index.js b/fixtures/eslint-v7/index.js index 6d4a7235dd..23af473216 100644 --- a/fixtures/eslint-v7/index.js +++ b/fixtures/eslint-v7/index.js @@ -141,3 +141,27 @@ function useHookInLoops() { useHook4(); } } + +/** + * Compiler Rules + */ +// Invalid: component factory +function InvalidComponentFactory() { + const DynamicComponent = () =>
Hello
; + // eslint-disable-next-line react-hooks/static-components + return ; +} + +// Invalid: mutating globals +function InvalidGlobals() { + // eslint-disable-next-line react-hooks/immutability + window.myGlobal = 42; + return
Done
; +} + +// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization +function InvalidUseMemo({items}) { + // eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps + const sorted = useMemo(() => [...items].sort(), []); + return
{sorted.length}
; +} diff --git a/fixtures/eslint-v7/yarn.lock b/fixtures/eslint-v7/yarn.lock index d2323b7c0c..4a191de947 100644 --- a/fixtures/eslint-v7/yarn.lock +++ b/fixtures/eslint-v7/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -17,160 +9,11 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/compat-data@^7.26.5": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" - integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== - -"@babel/core@^7.24.4": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" - integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/traverse" "^7.26.10" - "@babel/types" "^7.26.10" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" - integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== - dependencies: - "@babel/parser" "^7.26.10" - "@babel/types" "^7.26.10" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" - integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== - dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" - integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.26.9" - semver "^6.3.1" - -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-plugin-utils@^7.25.9": - version "7.26.5" - 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-replace-supers@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - 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-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helpers@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" - integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== - dependencies: - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - "@babel/highlight@^7.10.4": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" @@ -181,51 +24,6 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.24.4", "@babel/parser@^7.26.10", "@babel/parser@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== - dependencies: - "@babel/types" "^7.26.10" - -"@babel/plugin-transform-private-methods@^7.24.4": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/template@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" - integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.26.9" - "@babel/types" "^7.26.9" - -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" - integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" - integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -255,38 +53,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -366,26 +132,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -browserslist@^4.24.0: - version "4.24.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" - integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== - dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001688: - version "1.0.30001703" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718" - integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ== - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -432,11 +183,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - cross-spawn@^7.0.2: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -446,7 +192,7 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@^4.0.1, debug@^4.1.1: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -465,11 +211,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.5.73: - version "1.5.114" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz#f2bb4fda80a7db4ea273565e75b0ebbe19af0ac3" - integrity sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -483,11 +224,6 @@ enquirer@^2.3.5: ansi-colors "^4.1.1" strip-ansi "^6.0.1" -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -667,11 +403,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -691,11 +422,6 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^13.6.0, globals@^13.9.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" @@ -713,18 +439,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -791,11 +505,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -816,11 +525,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -846,13 +550,6 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -870,11 +567,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -911,7 +603,7 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -picocolors@^1.0.0, picocolors@^1.1.1: +picocolors@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -953,11 +645,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - semver@^7.2.1: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" @@ -1052,14 +739,6 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -update-browserslist-db@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -1088,18 +767,3 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -zod-validation-error@^3.0.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6" - integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== - -zod@^3.22.4: - version "3.24.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" - integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== diff --git a/fixtures/eslint-v8/.eslintrc.json b/fixtures/eslint-v8/.eslintrc.json index 33b2cfaaca..f76a20fea5 100644 --- a/fixtures/eslint-v8/.eslintrc.json +++ b/fixtures/eslint-v8/.eslintrc.json @@ -1,14 +1,11 @@ { "root": true, - "extends": ["plugin:react-hooks/recommended-legacy"], + "extends": ["plugin:react-hooks/recommended-latest-legacy"], "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } - }, - "rules": { - "react-hooks/exhaustive-deps": "error" } } diff --git a/fixtures/eslint-v8/index.js b/fixtures/eslint-v8/index.js index 6d4a7235dd..23af473216 100644 --- a/fixtures/eslint-v8/index.js +++ b/fixtures/eslint-v8/index.js @@ -141,3 +141,27 @@ function useHookInLoops() { useHook4(); } } + +/** + * Compiler Rules + */ +// Invalid: component factory +function InvalidComponentFactory() { + const DynamicComponent = () =>
Hello
; + // eslint-disable-next-line react-hooks/static-components + return ; +} + +// Invalid: mutating globals +function InvalidGlobals() { + // eslint-disable-next-line react-hooks/immutability + window.myGlobal = 42; + return
Done
; +} + +// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization +function InvalidUseMemo({items}) { + // eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps + const sorted = useMemo(() => [...items].sort(), []); + return
{sorted.length}
; +} diff --git a/fixtures/eslint-v8/yarn.lock b/fixtures/eslint-v8/yarn.lock index b2261f0e73..d5a947b634 100644 --- a/fixtures/eslint-v8/yarn.lock +++ b/fixtures/eslint-v8/yarn.lock @@ -2,213 +2,6 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/compat-data@^7.26.5": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" - integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== - -"@babel/core@^7.24.4": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" - integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/traverse" "^7.26.10" - "@babel/types" "^7.26.10" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" - integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== - dependencies: - "@babel/parser" "^7.26.10" - "@babel/types" "^7.26.10" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" - integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== - dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" - integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.26.9" - semver "^6.3.1" - -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-plugin-utils@^7.25.9": - version "7.26.5" - 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-replace-supers@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - 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-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/helper-validator-option@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== - -"@babel/helpers@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" - integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== - dependencies: - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - -"@babel/parser@^7.24.4", "@babel/parser@^7.26.10", "@babel/parser@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== - dependencies: - "@babel/types" "^7.26.10" - -"@babel/plugin-transform-private-methods@^7.24.4": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/template@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" - integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/parser" "^7.26.9" - "@babel/types" "^7.26.9" - -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" - integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== - dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" - integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== - dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@eslint-community/eslint-utils@^4.2.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -260,38 +53,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -368,26 +129,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -browserslist@^4.24.0: - version "4.24.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" - integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== - dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001688: - version "1.0.30001703" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718" - integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ== - chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -413,11 +159,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - cross-spawn@^7.0.2: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -427,7 +168,7 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.1.0, debug@^4.3.1, debug@^4.3.2: +debug@^4.3.1, debug@^4.3.2: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -446,16 +187,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -electron-to-chromium@^1.5.73: - version "1.5.114" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz#f2bb4fda80a7db4ea273565e75b0ebbe19af0ac3" - integrity sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA== - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -611,11 +342,6 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -635,11 +361,6 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^13.19.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" @@ -657,18 +378,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -722,11 +431,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -734,11 +438,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -754,11 +453,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -786,13 +480,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -817,11 +504,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -877,11 +559,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -picocolors@^1.0.0, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -921,11 +598,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -974,14 +646,6 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -update-browserslist-db@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -1006,22 +670,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod-validation-error@^3.0.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6" - integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== - -zod@^3.22.4: - version "3.24.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" - integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== diff --git a/fixtures/eslint-v9/eslint.config.ts b/fixtures/eslint-v9/eslint.config.ts index 66d2c08746..c13899e85b 100644 --- a/fixtures/eslint-v9/eslint.config.ts +++ b/fixtures/eslint-v9/eslint.config.ts @@ -1,8 +1,6 @@ import {defineConfig} from 'eslint/config'; import reactHooks from 'eslint-plugin-react-hooks'; -console.log(reactHooks.configs['recommended-latest']); - export default defineConfig([ { languageOptions: { diff --git a/fixtures/eslint-v9/index.js b/fixtures/eslint-v9/index.js index 6d4a7235dd..23af473216 100644 --- a/fixtures/eslint-v9/index.js +++ b/fixtures/eslint-v9/index.js @@ -141,3 +141,27 @@ function useHookInLoops() { useHook4(); } } + +/** + * Compiler Rules + */ +// Invalid: component factory +function InvalidComponentFactory() { + const DynamicComponent = () =>
Hello
; + // eslint-disable-next-line react-hooks/static-components + return ; +} + +// Invalid: mutating globals +function InvalidGlobals() { + // eslint-disable-next-line react-hooks/immutability + window.myGlobal = 42; + return
Done
; +} + +// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization +function InvalidUseMemo({items}) { + // eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps + const sorted = useMemo(() => [...items].sort(), []); + return
{sorted.length}
; +} diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock index a471aadd96..93756d4901 100644 --- a/fixtures/eslint-v9/yarn.lock +++ b/fixtures/eslint-v9/yarn.lock @@ -2,212 +2,209 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== +"@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: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" + "@babel/helper-validator-identifier" "^7.27.1" js-tokens "^4.0.0" - picocolors "^1.0.0" + picocolors "^1.1.1" -"@babel/compat-data@^7.26.5": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" - integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== "@babel/core@^7.24.4": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" - integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helpers" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/traverse" "^7.26.10" - "@babel/types" "^7.26.10" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" - integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== +"@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.26.10" - "@babel/types" "^7.26.10" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" + "@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/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== +"@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.25.9" + "@babel/types" "^7.27.3" -"@babel/helper-compilation-targets@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" - integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== dependencies: - "@babel/compat-data" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" - integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== +"@babel/helper-create-class-features-plugin@^7.18.6": + 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.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.26.9" + "@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-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== +"@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-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.25.9" - "@babel/types" "^7.25.9" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" -"@babel/helper-module-imports@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" - integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" -"@babel/helper-module-transforms@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" - integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== +"@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.25.9" + "@babel/types" "^7.27.1" -"@babel/helper-plugin-utils@^7.25.9": - version "7.26.5" - 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.18.6": + 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-replace-supers@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== +"@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.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== +"@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.25.9" - "@babel/types" "^7.25.9" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" -"@babel/helper-string-parser@^7.25.9": - version "7.25.9" - 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.25.9": - version "7.25.9" - 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.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" - integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== -"@babel/helpers@^7.26.10": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" - integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== dependencies: - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" -"@babel/parser@^7.24.4", "@babel/parser@^7.26.10", "@babel/parser@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" - integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== +"@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== dependencies: - "@babel/types" "^7.26.10" + "@babel/types" "^7.28.4" -"@babel/plugin-transform-private-methods@^7.24.4": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/template@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" - integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== +"@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.26.2" - "@babel/parser" "^7.26.9" - "@babel/types" "^7.26.9" + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" - integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== dependencies: - "@babel/code-frame" "^7.26.2" - "@babel/generator" "^7.26.10" - "@babel/parser" "^7.26.10" - "@babel/template" "^7.26.9" - "@babel/types" "^7.26.10" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" debug "^4.3.1" - globals "^11.1.0" -"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9": - version "7.26.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" - integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== dependencies: - "@babel/helper-string-parser" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" "@eslint-community/eslint-utils@^4.2.0": version "4.4.1" @@ -303,13 +300,20 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + 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/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": @@ -317,20 +321,15 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -387,6 +386,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +baseline-browser-mapping@^2.8.9: + version "2.8.10" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz#32eb5e253d633fa3fa3ffb1685fabf41680d9e8a" + integrity sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -396,24 +400,25 @@ brace-expansion@^1.1.7: concat-map "0.0.1" browserslist@^4.24.0: - version "4.24.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" - integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + version "4.26.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56" + integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w== dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" - update-browserslist-db "^1.1.1" + baseline-browser-mapping "^2.8.9" + caniuse-lite "^1.0.30001746" + electron-to-chromium "^1.5.227" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -caniuse-lite@^1.0.30001688: - version "1.0.30001703" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718" - integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ== +caniuse-lite@^1.0.30001746: + version "1.0.30001746" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz#199d20f04f5369825e00ff7067d45d5dfa03aee7" + integrity sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA== chalk@^4.0.0: version "4.1.2" @@ -454,7 +459,14 @@ cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.1.0, debug@^4.3.1, debug@^4.3.2: +debug@^4.1.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@^4.3.1, debug@^4.3.2: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -466,10 +478,10 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -electron-to-chromium@^1.5.73: - version "1.5.114" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz#f2bb4fda80a7db4ea273565e75b0ebbe19af0ac3" - integrity sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA== +electron-to-chromium@^1.5.227: + version "1.5.228" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz#38b849bc8714bd21fb64f5ad56bf8cfd8638e1e9" + integrity sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA== escalade@^3.2.0: version "3.2.0" @@ -646,11 +658,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" @@ -801,10 +808,10 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== optionator@^0.9.3: version "0.9.4" @@ -849,7 +856,7 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -picocolors@^1.0.0, picocolors@^1.1.1: +picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -905,7 +912,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -update-browserslist-db@^1.1.1: +update-browserslist-db@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== @@ -943,11 +950,11 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zod-validation-error@^3.0.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6" - integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== + version "3.5.3" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.5.3.tgz#85ba33290200d8db9f043621e284f40dddefb7e5" + integrity sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw== zod@^3.22.4: - version "3.24.2" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" - integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index 20d32fe9fd..2e965464c1 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -1,8 +1,6 @@ # `eslint-plugin-react-hooks` -This ESLint plugin enforces the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). - -It is a part of the [Hooks API](https://react.dev/reference/react/hooks) for React. +The official ESLint plugin for [React](https://react.dev) which enforces the [Rules of React](https://react.dev/reference/eslint-plugin-react-hooks) and other best practices. ## Installation diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index c01e22fb4b..81c21a22e0 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -7,7 +7,11 @@ import type {Linter, Rule} from 'eslint'; import ExhaustiveDeps from './rules/ExhaustiveDeps'; -import {allRules} from './shared/ReactCompiler'; +import { + allRules, + mapErrorSeverityToESlint, + recommendedRules, +} from './shared/ReactCompiler'; import RulesOfHooks from './rules/RulesOfHooks'; // All rules @@ -19,24 +23,56 @@ const rules = { ), } satisfies Record; -// Config rules -const ruleConfigs = { +// Basic hooks rules (for recommended config) +const basicRuleConfigs = { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', -} satisfies Linter.RulesRecord; +} as const satisfies Linter.RulesRecord; + +const compilerRuleConfigs = Object.fromEntries( + Object.entries(recommendedRules).map(([name, ruleConfig]) => { + return [ + `react-hooks/${name}` as const, + mapErrorSeverityToESlint(ruleConfig.severity), + ] as const; + }), +) as Record<`react-hooks/${string}`, Linter.RuleEntry>; + +// All rules including compiler rules (for recommended-latest config) +const allRuleConfigs: Linter.RulesRecord = { + ...basicRuleConfigs, + ...compilerRuleConfigs, +}; const plugin = { meta: { name: 'eslint-plugin-react-hooks', }, - configs: {}, rules, + configs: {} as { + 'recommended-legacy': { + plugins: Array; + rules: Linter.RulesRecord; + }; + 'recommended-latest-legacy': { + plugins: Array; + rules: Linter.RulesRecord; + }; + 'flat/recommended': Array; + 'recommended-latest': Array; + recommended: Array; + }, }; Object.assign(plugin.configs, { 'recommended-legacy': { plugins: ['react-hooks'], - rules: ruleConfigs, + rules: basicRuleConfigs, + }, + + 'recommended-latest-legacy': { + plugins: ['react-hooks'], + rules: allRuleConfigs, }, 'flat/recommended': [ @@ -44,7 +80,7 @@ Object.assign(plugin.configs, { plugins: { 'react-hooks': plugin, }, - rules: ruleConfigs, + rules: basicRuleConfigs, }, ], @@ -53,14 +89,18 @@ Object.assign(plugin.configs, { plugins: { 'react-hooks': plugin, }, - rules: ruleConfigs, + rules: allRuleConfigs, }, ], - recommended: { - plugins: ['react-hooks'], - rules: ruleConfigs, - }, + recommended: [ + { + plugins: { + 'react-hooks': plugin, + }, + rules: basicRuleConfigs, + }, + ], }); export default plugin; diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts index dec46457cc..cdb3af3848 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts @@ -160,38 +160,6 @@ function makeRule(rule: LintRule): Rule.RuleModule { }; } -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 RulesConfig = { [name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity}; }; @@ -201,12 +169,7 @@ export const allRules: RulesConfig = LintRules.reduce( acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; return acc; }, - { - 'no-unused-directives': { - rule: NoUnusedDirectivesRule, - severity: ErrorSeverity.Error, - }, - } as RulesConfig, + {} as RulesConfig, ); export const recommendedRules: RulesConfig = LintRules.filter( @@ -216,12 +179,7 @@ export const recommendedRules: RulesConfig = LintRules.filter( acc[rule.name] = {rule: makeRule(rule), severity: rule.severity}; return acc; }, - { - 'no-unused-directives': { - rule: NoUnusedDirectivesRule, - severity: ErrorSeverity.Error, - }, - } as RulesConfig, + {} as RulesConfig, ); export function mapErrorSeverityToESlint( diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index f46528cd60..fc9067d274 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -6,21 +6,19 @@ */ /* eslint-disable no-for-of-loops/no-for-of-loops */ -import {transformFromAstSync, traverse} from '@babel/core'; +import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; -import {Directive, File} from '@babel/types'; +import {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'; @@ -46,17 +44,11 @@ const COMPILER_OPTIONS: PluginOptions = { }, }; -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; }; @@ -88,24 +80,6 @@ function getFlowSuppressions( 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, @@ -126,7 +100,6 @@ function runReactCompilerImpl({ filename, userOpts, flowSuppressions: [], - unusedOptOutDirectives: [], events: [], }; const userLogger: Logger | null = options.logger; @@ -182,28 +155,6 @@ function runReactCompilerImpl({ 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 */ } diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js index 2c20bbcd1c..be2e0377d4 100644 --- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js +++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js @@ -850,6 +850,7 @@ describe('Timeline profiler', () => { `); }); + // @reactVersion >= 19.1 // @reactVersion < 19.2 it('should process a sample createRoot render sequence', async () => { function App() { @@ -2158,6 +2159,7 @@ describe('Timeline profiler', () => { `); }); + // @reactVersion >= 19.1 // @reactVersion < 19.2 it('should process a sample createRoot render sequence', async () => { function App() { diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 9de9b564e9..d5280c091c 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -974,12 +974,8 @@ describe('Store', () => { `); - const rendererID = getRendererID(); - const rootID = store.getRootIDForElement(store.getElementIDAtIndex(0)); await actAsync(() => { agent.overrideSuspenseMilestone({ - rendererID, - rootID, suspendedSet: [ store.getElementIDAtIndex(4), store.getElementIDAtIndex(8), @@ -1009,8 +1005,6 @@ describe('Store', () => { await actAsync(() => { agent.overrideSuspenseMilestone({ - rendererID, - rootID, suspendedSet: [], }); }); diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 5a7d6eb836..fce8fe626d 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -8,7 +8,11 @@ */ import EventEmitter from '../events'; -import {SESSION_STORAGE_LAST_SELECTION_KEY, __DEBUG__} from '../constants'; +import { + SESSION_STORAGE_LAST_SELECTION_KEY, + UNKNOWN_SUSPENDERS_NONE, + __DEBUG__, +} from '../constants'; import setupHighlighter from './views/Highlighter'; import { initialize as setupTraceUpdates, @@ -26,8 +30,13 @@ import type { RendererID, RendererInterface, DevToolsHookSettings, + InspectedElement, } from './types'; -import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types'; +import type { + ComponentFilter, + DehydratedData, + ElementType, +} from 'react-devtools-shared/src/frontend/types'; import type {GroupItem} from './views/TraceUpdates/canvas'; import {gte, isReactNativeEnvironment} from './utils'; import { @@ -73,6 +82,13 @@ type InspectElementParams = { requestID: number, }; +type InspectScreenParams = { + forceFullData: boolean, + id: number, + path: Array | null, + requestID: number, +}; + type OverrideHookParams = { id: number, hookID: number, @@ -131,8 +147,6 @@ type OverrideSuspenseParams = { }; type OverrideSuspenseMilestoneParams = { - rendererID: number, - rootID: number, suspendedSet: Array, }; @@ -141,6 +155,111 @@ type PersistedSelection = { path: Array, }; +function createEmptyInspectedScreen( + arbitraryRootID: number, + type: ElementType, +): InspectedElement { + const suspendedBy: DehydratedData = { + cleaned: [], + data: [], + unserializable: [], + }; + return { + // invariants + id: arbitraryRootID, + type: type, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy, + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; +} + +function mergeRoots( + left: InspectedElement, + right: InspectedElement, + suspendedByOffset: number, +): void { + const leftSuspendedByRange = left.suspendedByRange; + const rightSuspendedByRange = right.suspendedByRange; + + if (right.isErrored) { + left.isErrored = true; + } + for (let i = 0; i < right.errors.length; i++) { + left.errors.push(right.errors[i]); + } + for (let i = 0; i < right.warnings.length; i++) { + left.warnings.push(right.warnings[i]); + } + + const leftSuspendedBy: DehydratedData = left.suspendedBy; + const {data, cleaned, unserializable} = (right.suspendedBy: DehydratedData); + const leftSuspendedByData = ((leftSuspendedBy.data: any): Array); + const rightSuspendedByData = ((data: any): Array); + for (let i = 0; i < rightSuspendedByData.length; i++) { + leftSuspendedByData.push(rightSuspendedByData[i]); + } + for (let i = 0; i < cleaned.length; i++) { + leftSuspendedBy.cleaned.push( + [suspendedByOffset + cleaned[i][0]].concat(cleaned[i].slice(1)), + ); + } + for (let i = 0; i < unserializable.length; i++) { + leftSuspendedBy.unserializable.push( + [suspendedByOffset + unserializable[i][0]].concat( + unserializable[i].slice(1), + ), + ); + } + + if (rightSuspendedByRange !== null) { + if (leftSuspendedByRange === null) { + left.suspendedByRange = [ + rightSuspendedByRange[0], + rightSuspendedByRange[1], + ]; + } else { + if (rightSuspendedByRange[0] < leftSuspendedByRange[0]) { + leftSuspendedByRange[0] = rightSuspendedByRange[0]; + } + if (rightSuspendedByRange[1] > leftSuspendedByRange[1]) { + leftSuspendedByRange[1] = rightSuspendedByRange[1]; + } + } + } +} + export default class Agent extends EventEmitter<{ hideNativeHighlight: [], showNativeHighlight: [HostInstance], @@ -201,6 +320,7 @@ export default class Agent extends EventEmitter<{ bridge.addListener('getProfilingStatus', this.getProfilingStatus); bridge.addListener('getOwnersList', this.getOwnersList); bridge.addListener('inspectElement', this.inspectElement); + bridge.addListener('inspectScreen', this.inspectScreen); bridge.addListener('logElementToConsole', this.logElementToConsole); bridge.addListener('overrideError', this.overrideError); bridge.addListener('overrideSuspense', this.overrideSuspense); @@ -531,6 +651,138 @@ export default class Agent extends EventEmitter<{ } }; + inspectScreen: InspectScreenParams => void = ({ + requestID, + id, + forceFullData, + path: screenPath, + }) => { + let inspectedScreen: InspectedElement | null = null; + let found = false; + // the suspendedBy index will be from the previously merged roots. + // We need to keep track of how many suspendedBy we've already seen to know + // to which renderer the index belongs. + let suspendedByOffset = 0; + let suspendedByPathIndex: number | null = null; + // The path to hydrate for a specific renderer + let rendererPath: InspectElementParams['path'] = null; + if (screenPath !== null && screenPath.length > 1) { + const secondaryCategory = screenPath[0]; + if (secondaryCategory !== 'suspendedBy') { + throw new Error( + 'Only hydrating suspendedBy paths is supported. This is a bug.', + ); + } + if (typeof screenPath[1] !== 'number') { + throw new Error( + `Expected suspendedBy index to be a number. Received '${screenPath[1]}' instead. This is a bug.`, + ); + } + suspendedByPathIndex = screenPath[1]; + rendererPath = screenPath.slice(2); + } + + for (const rendererID in this._rendererInterfaces) { + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); + let path: InspectElementParams['path'] = null; + if (suspendedByPathIndex !== null && rendererPath !== null) { + const suspendedByPathRendererIndex = + suspendedByPathIndex - suspendedByOffset; + const rendererHasRequestedSuspendedByPath = + renderer.getElementAttributeByPath(id, [ + 'suspendedBy', + suspendedByPathRendererIndex, + ]) !== undefined; + if (rendererHasRequestedSuspendedByPath) { + path = ['suspendedBy', suspendedByPathRendererIndex].concat( + rendererPath, + ); + } + } + + const inspectedRootsPayload = renderer.inspectElement( + requestID, + id, + path, + forceFullData, + ); + switch (inspectedRootsPayload.type) { + case 'hydrated-path': + // The path will be relative to the Roots of this renderer. We adjust it + // to be relative to all Roots of this implementation. + inspectedRootsPayload.path[1] += suspendedByOffset; + // TODO: Hydration logic is flawed since the Frontend path is not based + // on the original backend data but rather its own representation of it (e.g. due to reorder). + // So we can receive null here instead when hydration fails + if (inspectedRootsPayload.value !== null) { + for ( + let i = 0; + i < inspectedRootsPayload.value.cleaned.length; + i++ + ) { + inspectedRootsPayload.value.cleaned[i][1] += suspendedByOffset; + } + } + this._bridge.send('inspectedScreen', inspectedRootsPayload); + // If we hydrated a path, it must've been in a specific renderer so we can stop here. + return; + case 'full-data': + const inspectedRoots = inspectedRootsPayload.value; + if (inspectedScreen === null) { + inspectedScreen = createEmptyInspectedScreen( + inspectedRoots.id, + inspectedRoots.type, + ); + } + mergeRoots(inspectedScreen, inspectedRoots, suspendedByOffset); + const dehydratedSuspendedBy: DehydratedData = + inspectedRoots.suspendedBy; + const suspendedBy = ((dehydratedSuspendedBy.data: any): Array); + suspendedByOffset += suspendedBy.length; + found = true; + break; + case 'no-change': + found = true; + const rootsSuspendedBy: Array = + (renderer.getElementAttributeByPath(id, ['suspendedBy']): any); + suspendedByOffset += rootsSuspendedBy.length; + break; + case 'not-found': + break; + case 'error': + // bail out and show the error + // TODO: aggregate errors + this._bridge.send('inspectedScreen', inspectedRootsPayload); + return; + } + } + + if (inspectedScreen === null) { + if (found) { + this._bridge.send('inspectedScreen', { + type: 'no-change', + responseID: requestID, + id, + }); + } else { + this._bridge.send('inspectedScreen', { + type: 'not-found', + responseID: requestID, + id, + }); + } + } else { + this._bridge.send('inspectedScreen', { + type: 'full-data', + responseID: requestID, + id, + value: inspectedScreen, + }); + } + }; + logElementToConsole: ElementAndRendererID => void = ({id, rendererID}) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { @@ -567,17 +819,15 @@ export default class Agent extends EventEmitter<{ }; overrideSuspenseMilestone: OverrideSuspenseMilestoneParams => void = ({ - rendererID, - rootID, suspendedSet, }) => { - const renderer = this._rendererInterfaces[rendererID]; - if (renderer == null) { - console.warn( - `Invalid renderer id "${rendererID}" to override suspense milestone`, - ); - } else { - renderer.overrideSuspenseMilestone(rootID, suspendedSet); + for (const rendererID in this._rendererInterfaces) { + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); + if (renderer.supportsTogglingSuspense) { + renderer.overrideSuspenseMilestone(suspendedSet); + } } }; @@ -739,7 +989,7 @@ export default class Agent extends EventEmitter<{ if (renderer !== null) { const devRenderer = renderer.bundleType === 1; const enableSuspenseTab = - devRenderer && gte(renderer.version, '19.2.0-canary'); + devRenderer && gte(renderer.version, '19.3.0-canary'); if (enableSuspenseTab) { this._bridge.send('enableSuspenseTab'); } diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 031a18b74d..ba214db05c 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2420,7 +2420,6 @@ export function attach( !isProductionBuildOfRenderer && StrictModeBits !== 0 ? 1 : 0, ); pushOperation(hasOwnerMetadata ? 1 : 0); - pushOperation(supportsTogglingSuspense ? 1 : 0); if (isProfiling) { if (displayNamesByRootID !== null) { @@ -4902,7 +4901,11 @@ export function attach( fiberInstance.data = nextFiber; if ( mostRecentlyInspectedElement !== null && - mostRecentlyInspectedElement.id === fiberInstance.id && + (mostRecentlyInspectedElement.id === fiberInstance.id || + // If we're inspecting a Root, we inspect the Screen. + // Invalidating any Root invalidates the Screen too. + (mostRecentlyInspectedElement.type === ElementTypeRoot && + nextFiber.tag === HostRoot)) && didFiberRender(prevFiber, nextFiber) ) { // If this Fiber has updated, clear cached inspected data. @@ -6422,7 +6425,10 @@ export function attach( return inspectVirtualInstanceRaw(devtoolsInstance); } if (devtoolsInstance.kind === FIBER_INSTANCE) { - return inspectFiberInstanceRaw(devtoolsInstance); + const isRoot = devtoolsInstance.parent === null; + return isRoot + ? inspectRootsRaw(devtoolsInstance.id) + : inspectFiberInstanceRaw(devtoolsInstance); } (devtoolsInstance: FilteredFiberInstance); // assert exhaustive throw new Error('Unsupported instance kind'); @@ -6590,9 +6596,6 @@ export function attach( rootType = fiberRoot._debugRootType; } - const isTimedOutSuspense = - tag === SuspenseComponent && memoizedState !== null; - let isErrored = false; if (isErrorBoundary(fiber)) { // if the current inspected element is an error boundary, @@ -6664,7 +6667,7 @@ export function attach( if ( fiberInstance.suspenseNode !== null && fiberInstance.suspenseNode.hasUnknownSuspenders && - !isTimedOutSuspense + !isSuspended ) { // Something unknown threw to suspended this boundary. Let's figure out why that might be. if (renderer.bundleType === 0) { @@ -6702,7 +6705,7 @@ export function attach( supportsTogglingSuspense && hasSuspenseBoundary && // If it's showing the real content, we can always flip fallback. - (!isTimedOutSuspense || + (!isSuspended || // If it's showing fallback because we previously forced it to, // allow toggling it back to remove the fallback override. forceFallbackForFibers.has(fiber) || @@ -6875,10 +6878,24 @@ export function attach( let currentlyInspectedPaths: Object = {}; function isMostRecentlyInspectedElement(id: number): boolean { - return ( - mostRecentlyInspectedElement !== null && - mostRecentlyInspectedElement.id === id - ); + if (mostRecentlyInspectedElement === null) { + return false; + } + if (mostRecentlyInspectedElement.id === id) { + return true; + } + + if (mostRecentlyInspectedElement.type === ElementTypeRoot) { + // we inspected the screen recently. If we're inspecting another root, we're + // still inspecting the screen. + const instance = idToDevToolsInstanceMap.get(id); + return ( + instance !== undefined && + instance.kind === FIBER_INSTANCE && + instance.parent === null + ); + } + return false; } function isMostRecentlyInspectedElementCurrent(id: number): boolean { @@ -7060,8 +7077,8 @@ export function attach( if (!hasElementUpdatedSinceLastInspected) { if (path !== null) { let secondaryCategory: 'suspendedBy' | 'hooks' | null = null; - if (path[0] === 'hooks') { - secondaryCategory = 'hooks'; + if (path[0] === 'hooks' || path[0] === 'suspendedBy') { + secondaryCategory = path[0]; } // If this element has not been updated since it was last inspected, @@ -7209,6 +7226,99 @@ export function attach( }; } + function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null { + const roots = hook.getFiberRoots(rendererID); + if (roots.size === 0) { + return null; + } + + const inspectedRoots: InspectedElement = { + // invariants + id: arbitraryRootID, + type: ElementTypeRoot, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy: [], + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; + + let minSuspendedByRange = Infinity; + let maxSuspendedByRange = -Infinity; + roots.forEach(root => { + const rootInstance = rootToFiberInstanceMap.get(root); + if (rootInstance === undefined) { + throw new Error( + 'Expected a root instance to exist for this Fiber root', + ); + } + const inspectedRoot = inspectFiberInstanceRaw(rootInstance); + if (inspectedRoot === null) { + return; + } + + if (inspectedRoot.isErrored) { + inspectedRoots.isErrored = true; + } + for (let i = 0; i < inspectedRoot.errors.length; i++) { + inspectedRoots.errors.push(inspectedRoot.errors[i]); + } + for (let i = 0; i < inspectedRoot.warnings.length; i++) { + inspectedRoots.warnings.push(inspectedRoot.warnings[i]); + } + for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) { + inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]); + } + const suspendedByRange = inspectedRoot.suspendedByRange; + if (suspendedByRange !== null) { + if (suspendedByRange[0] < minSuspendedByRange) { + minSuspendedByRange = suspendedByRange[0]; + } + if (suspendedByRange[1] > maxSuspendedByRange) { + maxSuspendedByRange = suspendedByRange[1]; + } + } + }); + + if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) { + inspectedRoots.suspendedByRange = [ + minSuspendedByRange, + maxSuspendedByRange, + ]; + } + + return inspectedRoots; + } + function logElementToConsole(id: number) { const result = isMostRecentlyInspectedElementCurrent(id) ? mostRecentlyInspectedElement @@ -7867,13 +7977,9 @@ export function attach( /** * Resets the all other roots of this renderer. - * @param rootID The root that contains this milestone * @param suspendedSet List of IDs of SuspenseComponent Fibers */ - function overrideSuspenseMilestone( - rootID: FiberInstance['id'], - suspendedSet: Array, - ) { + function overrideSuspenseMilestone(suspendedSet: Array) { if ( typeof setSuspenseHandler !== 'function' || typeof scheduleUpdate !== 'function' @@ -7883,8 +7989,6 @@ export function attach( ); } - // TODO: Allow overriding the timeline for the specified root. - const unsuspendedSet: Set = new Set(forceFallbackForFibers); let resuspended = false; diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 8a245155ef..46709c9a80 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -412,7 +412,6 @@ export function attach( pushOperation(0); // Profiling flag pushOperation(0); // StrictMode supported? pushOperation(hasOwnerMetadata ? 1 : 0); - pushOperation(supportsTogglingSuspense ? 1 : 0); pushOperation(SUSPENSE_TREE_OPERATION_ADD); pushOperation(id); @@ -800,6 +799,20 @@ export function attach( return null; } + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + const isRoot = rootID === id; + return isRoot + ? inspectRootsRaw(rootID) + : inspectInternalInstanceRaw(id, internalInstance); + } + + function inspectInternalInstanceRaw( + id: number, + internalInstance: InternalInstance, + ): InspectedElement | null { const {key} = getData(internalInstance); const type = getElementType(internalInstance); @@ -903,6 +916,98 @@ export function attach( }; } + function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null { + const roots = + renderer.Mount._instancesByReactRootID || + renderer.Mount._instancesByContainerID; + + const inspectedRoots: InspectedElement = { + // invariants + id: arbitraryRootID, + type: ElementTypeRoot, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy: [], + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + // TODO: We could make the Frontend accept an array to display + // a list of unique renderers contributing to this Screen. + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; + + let minSuspendedByRange = Infinity; + let maxSuspendedByRange = -Infinity; + + for (const rootKey in roots) { + const internalInstance = roots[rootKey]; + const id = getID(internalInstance); + const inspectedRoot = inspectInternalInstanceRaw(id, internalInstance); + + if (inspectedRoot === null) { + return null; + } + + if (inspectedRoot.isErrored) { + inspectedRoots.isErrored = true; + } + for (let i = 0; i < inspectedRoot.errors.length; i++) { + inspectedRoots.errors.push(inspectedRoot.errors[i]); + } + for (let i = 0; i < inspectedRoot.warnings.length; i++) { + inspectedRoots.warnings.push(inspectedRoot.warnings[i]); + } + for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) { + inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]); + } + const suspendedByRange = inspectedRoot.suspendedByRange; + if (suspendedByRange !== null) { + if (suspendedByRange[0] < minSuspendedByRange) { + minSuspendedByRange = suspendedByRange[0]; + } + if (suspendedByRange[1] > maxSuspendedByRange) { + maxSuspendedByRange = suspendedByRange[1]; + } + } + } + + if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) { + inspectedRoots.suspendedByRange = [ + minSuspendedByRange, + maxSuspendedByRange, + ]; + } + + return inspectedRoots; + } + function logElementToConsole(id: number): void { const result = inspectElementRaw(id); if (result === null) { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index e002740cb6..1052dc9d75 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -450,10 +450,7 @@ export type RendererInterface = { onErrorOrWarning?: OnErrorOrWarning, overrideError: (id: number, forceError: boolean) => void, overrideSuspense: (id: number, forceFallback: boolean) => void, - overrideSuspenseMilestone: ( - rootID: number, - suspendedSet: Array, - ) => void, + overrideSuspenseMilestone: (suspendedSet: Array) => void, overrideValueAtPath: ( type: Type, id: number, diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index 155717e5d8..6fd93d3519 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -10,6 +10,7 @@ import Agent from 'react-devtools-shared/src/backend/agent'; import {hideOverlay, showOverlay} from './Highlighter'; +import type {HostInstance} from 'react-devtools-shared/src/backend/types'; import type {BackendBridge} from 'react-devtools-shared/src/bridge'; import type {RendererInterface} from '../../types'; @@ -26,6 +27,7 @@ export default function setupHighlighter( ): void { bridge.addListener('clearHostInstanceHighlight', clearHostInstanceHighlight); bridge.addListener('highlightHostInstance', highlightHostInstance); + bridge.addListener('highlightHostInstances', highlightHostInstances); bridge.addListener('scrollToHostInstance', scrollToHostInstance); bridge.addListener('shutdown', stopInspectingHost); bridge.addListener('startInspectingHost', startInspectingHost); @@ -157,6 +159,52 @@ export default function setupHighlighter( hideOverlay(agent); } + function highlightHostInstances({ + displayName, + hideAfterTimeout, + elements, + scrollIntoView, + }: { + displayName: string | null, + hideAfterTimeout: boolean, + elements: Array<{rendererID: number, id: number}>, + scrollIntoView: boolean, + }) { + const nodes: Array = []; + for (let i = 0; i < elements.length; i++) { + const {id, rendererID} = elements[i]; + const renderer = agent.rendererInterfaces[rendererID]; + if (renderer == null) { + console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); + continue; + } + + // In some cases fiber may already be unmounted + if (!renderer.hasElementWithId(id)) { + continue; + } + + const hostInstances = renderer.findHostInstancesForElementID(id); + if (hostInstances !== null) { + for (let j = 0; j < hostInstances.length; j++) { + nodes.push(hostInstances[j]); + } + } + } + + if (nodes.length > 0) { + const node = nodes[0]; + // $FlowFixMe[method-unbinding] + if (scrollIntoView && typeof node.scrollIntoView === 'function') { + // If the node isn't visible show it before highlighting it. + // We may want to reconsider this; it might be a little disruptive. + node.scrollIntoView({block: 'nearest', inline: 'nearest'}); + } + } + + showOverlay(nodes, displayName, agent, hideAfterTimeout); + } + function attemptScrollToHostInstance( renderer: RendererInterface, id: number, diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index f6c11fb10d..c332393d2a 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -95,7 +95,7 @@ export function inspectElement( id: number, path: InspectedElementPath | null, rendererID: number, - shouldListenToPauseEvents: boolean = false, + shouldListenToPauseEvents: boolean, ): Promise { const requestID = requestCounter++; const promise = getPromiseForRequestID( @@ -117,6 +117,32 @@ export function inspectElement( return promise; } +export function inspectScreen( + bridge: FrontendBridge, + forceFullData: boolean, + arbitraryRootID: number, + path: InspectedElementPath | null, + shouldListenToPauseEvents: boolean, +): Promise { + const requestID = requestCounter++; + const promise = getPromiseForRequestID( + requestID, + 'inspectedScreen', + bridge, + `Timed out while inspecting screen.`, + shouldListenToPauseEvents, + ); + + bridge.send('inspectScreen', { + requestID, + id: arbitraryRootID, + path, + forceFullData, + }); + + return promise; +} + let storeAsGlobalCount = 0; export function storeAsGlobal({ diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index aa9c867e1f..b6229192c2 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -65,12 +65,6 @@ export const BRIDGE_PROTOCOL: Array = [ { version: 2, minNpmVersion: '4.22.0', - maxNpmVersion: '6.2.0', - }, - // Version 3 adds supports-toggling-suspense bit to add-root - { - version: 3, - minNpmVersion: '6.2.0', maxNpmVersion: null, }, ]; @@ -92,6 +86,12 @@ type HighlightHostInstance = { openBuiltinElementsPanel: boolean, scrollIntoView: boolean, }; +type HighlightHostInstances = { + elements: Array, + displayName: string | null, + hideAfterTimeout: boolean, + scrollIntoView: boolean, +}; type ScrollToHostInstance = { ...ElementAndRendererID, @@ -145,8 +145,6 @@ type OverrideSuspense = { }; type OverrideSuspenseMilestone = { - rendererID: number, - rootID: number, suspendedSet: Array, }; @@ -167,6 +165,13 @@ type InspectElementParams = { requestID: number, }; +type InspectScreenParams = { + requestID: number, + id: number, + forceFullData: boolean, + path: Array | null, +}; + type StoreAsGlobalParams = { ...ElementAndRendererID, count: number, @@ -199,6 +204,7 @@ export type BackendEvents = { fastRefreshScheduled: [], getSavedPreferences: [], inspectedElement: [InspectedElementPayload], + inspectedScreen: [InspectedElementPayload], isReloadAndProfileSupportedByBackend: [boolean], operations: [Array], ownersList: [OwnersList], @@ -243,7 +249,9 @@ type FrontendEvents = { getProfilingData: [{rendererID: RendererID}], getProfilingStatus: [], highlightHostInstance: [HighlightHostInstance], + highlightHostInstances: [HighlightHostInstances], inspectElement: [InspectElementParams], + inspectScreen: [InspectScreenParams], logElementToConsole: [ElementAndRendererID], overrideError: [OverrideError], overrideSuspense: [OverrideSuspense], diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 0e3373e15e..99971f6a1a 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -96,7 +96,6 @@ export type Capabilities = { supportsBasicProfiling: boolean, hasOwnerMetadata: boolean, supportsStrictMode: boolean, - supportsTogglingSuspense: boolean, supportsAdvancedProfiling: AdvancedProfiling, }; @@ -506,14 +505,6 @@ export default class Store extends EventEmitter<{ ); } - supportsTogglingSuspense(rootID: Element['id']): boolean { - const capabilities = this._rootIDToCapabilities.get(rootID); - if (capabilities === undefined) { - throw new Error(`No capabilities registered for root ${rootID}`); - } - return capabilities.supportsTogglingSuspense; - } - // This build of DevTools supports the Timeline profiler. // This is a static flag, controlled by the Store config. get supportsTimeline(): boolean { @@ -898,38 +889,48 @@ export default class Store extends EventEmitter<{ * @param uniqueSuspendersOnly Filters out boundaries without unique suspenders */ getSuspendableDocumentOrderSuspense( - rootID: Element['id'] | void, uniqueSuspendersOnly: boolean, ): $ReadOnlyArray { - if (rootID === undefined) { - return []; - } - const root = this.getElementByID(rootID); - if (root === null) { - return []; - } - if (!this.supportsTogglingSuspense(rootID)) { + const roots = this.roots; + if (roots.length === 0) { return []; } + const list: SuspenseNode['id'][] = []; - const suspense = this.getSuspenseByID(rootID); - if (suspense !== null) { - const stack = [suspense]; - while (stack.length > 0) { - const current = stack.pop(); - if (current === undefined) { - continue; + for (let i = 0; i < roots.length; i++) { + const rootID = roots[i]; + const root = this.getElementByID(rootID); + if (root === null) { + continue; + } + // TODO: This includes boundaries that can't be suspended due to no support from the renderer. + + const suspense = this.getSuspenseByID(rootID); + if (suspense !== null) { + if (list.length === 0) { + // start with an arbitrary root that will allow inspection of the Screen + list.push(suspense.id); } - // Include the root even if we won't show it suspended (because that's just blank). - // You should be able to see what suspended the shell. - if (!uniqueSuspendersOnly || current.hasUniqueSuspenders) { - list.push(current.id); - } - // Add children in reverse order to maintain document order - for (let j = current.children.length - 1; j >= 0; j--) { - const childSuspense = this.getSuspenseByID(current.children[j]); - if (childSuspense !== null) { - stack.push(childSuspense); + + const stack = [suspense]; + while (stack.length > 0) { + const current = stack.pop(); + if (current === undefined) { + continue; + } + if ( + (!uniqueSuspendersOnly || current.hasUniqueSuspenders) && + // Roots are already included as part of the Screen + current.id !== rootID + ) { + list.push(current.id); + } + // Add children in reverse order to maintain document order + for (let j = current.children.length - 1; j >= 0; j--) { + const childSuspense = this.getSuspenseByID(current.children[j]); + if (childSuspense !== null) { + stack.push(childSuspense); + } } } } @@ -1191,7 +1192,6 @@ export default class Store extends EventEmitter<{ let supportsStrictMode = false; let hasOwnerMetadata = false; - let supportsTogglingSuspense = false; // If we don't know the bridge protocol, guess that we're dealing with the latest. // If we do know it, we can take it into consideration when parsing operations. @@ -1204,9 +1204,6 @@ export default class Store extends EventEmitter<{ hasOwnerMetadata = operations[i] > 0; i++; - - supportsTogglingSuspense = operations[i] > 0; - i++; } this._roots = this._roots.concat(id); @@ -1215,7 +1212,6 @@ export default class Store extends EventEmitter<{ supportsBasicProfiling, hasOwnerMetadata, supportsStrictMode, - supportsTogglingSuspense, supportsAdvancedProfiling, }); @@ -1561,7 +1557,12 @@ export default class Store extends EventEmitter<{ if (name === null) { // The boundary isn't explicitly named. // Pick a sensible default. - name = this._guessSuspenseName(element); + if (parentID === 0) { + // For Roots we use their display name. + name = element.displayName; + } else { + name = this._guessSuspenseName(element); + } } } 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 08bba4f9da..88aeab7bfd 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -269,18 +269,21 @@ export default function InspectedElementWrapper(_: Props): React.Node { )} - {canToggleSuspense && ( + {canToggleSuspense || isSuspended ? ( - )} + ) : null} {store.supportsInspectMatchingDOMElement && (
+ ); + } return null; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js deleted file mode 100644 index ebb4b5bcd5..0000000000 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js +++ /dev/null @@ -1,72 +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. - * - * @flow - */ - -import * as React from 'react'; -import {OptionsContext} from '../context'; -import EditableValue from './EditableValue'; -import Store from '../../store'; -import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types'; -import styles from './InspectedElementSharedStyles.css'; - -import type {InspectedElement} from 'react-devtools-shared/src/frontend/types'; -import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; - -type Props = { - bridge: FrontendBridge, - inspectedElement: InspectedElement, - store: Store, -}; - -export default function InspectedElementSuspenseToggle({ - bridge, - inspectedElement, - store, -}: Props): React.Node { - const {readOnly} = React.useContext(OptionsContext); - - const {id, isSuspended, type} = inspectedElement; - const canToggleSuspense = !readOnly && inspectedElement.canToggleSuspense; - - if (type !== ElementTypeSuspense) { - return null; - } - - const toggleSuspense = (path: any, value: boolean) => { - const rendererID = store.getRendererIDForElement(id); - if (rendererID !== null) { - bridge.send('overrideSuspense', { - id, - rendererID, - forceFallback: value, - }); - } - }; - - return ( -
-
-
suspense
-
-
- Suspended - {canToggleSuspense ? ( - // key is required to keep and header row toggle button in sync - - ) : ( - {isSuspended ? 'true' : 'false'} - )} -
-
- ); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index 7e2175db74..6d3d0d604f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -17,7 +17,6 @@ import InspectedElementHooksTree from './InspectedElementHooksTree'; import InspectedElementPropsTree from './InspectedElementPropsTree'; import InspectedElementStateTree from './InspectedElementStateTree'; import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin'; -import InspectedElementSuspenseToggle from './InspectedElementSuspenseToggle'; import InspectedElementSuspendedBy from './InspectedElementSuspendedBy'; import NativeStyleEditor from './NativeStyleEditor'; import {enableStyleXFeatures} from 'react-devtools-feature-flags'; @@ -25,6 +24,10 @@ import InspectedElementSourcePanel from './InspectedElementSourcePanel'; import StackTraceView from './StackTraceView'; import OwnerView from './OwnerView'; import Skeleton from './Skeleton'; +import { + ElementTypeSuspense, + ElementTypeActivity, +} from 'react-devtools-shared/src/frontend/types'; import styles from './InspectedElementView.css'; @@ -61,6 +64,7 @@ export default function InspectedElementView({ rootType, source, nativeTag, + type, } = inspectedElement; const bridge = useContext(BridgeContext); @@ -75,6 +79,17 @@ export default function InspectedElementView({ const showRenderedBy = showStack || showOwnersList || rendererLabel !== null || rootType !== null; + const propsSection = ( +
+ +
+ ); + return (
@@ -86,22 +101,12 @@ export default function InspectedElementView({ />
-
- -
- -
- -
+ { + // For Suspense and Activity we show the props further down. + type !== ElementTypeSuspense && type !== ElementTypeActivity + ? propsSection + : null + }
+ { + // For Suspense and Activity we show the props below suspended by to give that more priority. + type !== ElementTypeSuspense && type !== ElementTypeActivity + ? null + : propsSection + } + {showRenderedBy && (
0 ? (
  • + aria-current="true"> diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css index 7ad3152496..ba862051d9 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css @@ -1,5 +1,12 @@ .SuspenseRectsContainer { padding: .25rem; + cursor: pointer; + outline: 1px solid var(--color-component-name); + border-radius: 0.25rem; +} + +.SuspenseRectsContainer[data-highlighted='true'] { + background: var(--color-dimmest); } .SuspenseRectsViewBox { @@ -28,6 +35,8 @@ pointer-events: all; outline-style: solid; outline-width: 1px; + border-radius: 0.125rem; + cursor: pointer; } .SuspenseRectsScaledRect { @@ -42,7 +51,7 @@ /* highlight this boundary */ .SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect { - background-color: var(--color-background-hover); + background-color: var(--color-background-hover); } .SuspenseRectsRect[data-highlighted='true'] { diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index 00ca3e1459..8e43944e7a 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -278,11 +278,7 @@ function getDocumentBoundingRect( }; } -function SuspenseRectsShell({ - rootID, -}: { - rootID: SuspenseNode['id'], -}): React$Node { +function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node { const store = useContext(StoreContext); const root = store.getSuspenseByID(rootID); if (root === null) { @@ -299,6 +295,9 @@ const ViewBox = createContext((null: any)); function SuspenseRectsContainer(): React$Node { const store = useContext(StoreContext); + const {inspectedElementID} = useContext(TreeStateContext); + const treeDispatch = useContext(TreeDispatcherContext); + const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); // TODO: This relies on a full re-render of all children when the Suspense tree changes. const {roots} = useContext(SuspenseTreeStateContext); @@ -312,14 +311,38 @@ function SuspenseRectsContainer(): React$Node { const width = '100%'; const aspectRatio = `1 / ${heightScale}`; + function handleClick(event: SyntheticMouseEvent) { + if (event.defaultPrevented) { + // Already clicked on an inner rect + return; + } + if (roots.length === 0) { + // Nothing to select + return; + } + const arbitraryRootID = roots[0]; + + event.preventDefault(); + treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: arbitraryRootID}); + suspenseTreeDispatch({ + type: 'SET_SUSPENSE_LINEAGE', + payload: arbitraryRootID, + }); + } + + const isRootSelected = roots.includes(inspectedElementID); + return ( -
    +
    {roots.map(rootID => { - return ; + return ; })}
    diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css index 4668ede127..932a23103f 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css @@ -37,7 +37,7 @@ padding-right: 0; } -.SuspenseScrubberBead, .SuspenseScrubberBeadSelected { +.SuspenseScrubberBead { flex: 1; height: 0.5rem; background: var(--color-background-selected); @@ -51,9 +51,11 @@ background: var(--color-background-selected); } +.SuspenseScrubberBeadTransition { + background: var(--color-component-name); +} + .SuspenseScrubberStepHighlight > .SuspenseScrubberBead, -.SuspenseScrubberStepHighlight > .SuspenseScrubberBeadSelected, -.SuspenseScrubberStep:hover > .SuspenseScrubberBead, -.SuspenseScrubberStep:hover > .SuspenseScrubberBeadSelected { +.SuspenseScrubberStep:hover > .SuspenseScrubberBead { height: 0.75rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js index cbb76e4164..53d20b6467 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js @@ -14,6 +14,8 @@ import {useRef} from 'react'; import styles from './SuspenseScrubber.css'; +import Tooltip from '../Components/reach-ui/tooltip'; + export default function SuspenseScrubber({ min, max, @@ -53,24 +55,38 @@ export default function SuspenseScrubber({ const steps = []; for (let index = min; index <= max; index++) { steps.push( -
    + label={ + index === min + ? // The first step in the timeline is always a Transition (Initial Paint). + // TODO: Support multiple environments. + 'Initial Paint' + : // TODO: Consider adding the name of this specific boundary if this step has only one. + 'Suspense' + }>
    -
    , + onPointerDown={handlePress.bind(null, index)} + onMouseEnter={onHoverSegment.bind(null, index)}> +
    +
    + , ); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js index b4ed7ec1f9..f698f0ae16 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js @@ -34,13 +34,13 @@ import { SuspenseTreeStateContext, } from './SuspenseTreeContext'; import {StoreContext, OptionsContext} from '../context'; -import {TreeDispatcherContext} from '../Components/TreeContext'; +import { + TreeDispatcherContext, + TreeStateContext, +} from '../Components/TreeContext'; import Button from '../Button'; import Toggle from '../Toggle'; -import typeof { - SyntheticEvent, - SyntheticPointerEvent, -} from 'react-dom-bindings/src/events/SyntheticEvent'; +import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext'; @@ -71,20 +71,14 @@ function ToggleUniqueSuspenders() { const store = useContext(StoreContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - const {selectedRootID: rootID, uniqueSuspendersOnly} = useContext( - SuspenseTreeStateContext, - ); + const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext); function handleToggleUniqueSuspenders() { const nextUniqueSuspendersOnly = !uniqueSuspendersOnly; - const nextTimeline = - rootID === null - ? [] - : // TODO: Handle different timeline modes (e.g. random order) - store.getSuspendableDocumentOrderSuspense( - rootID, - nextUniqueSuspendersOnly, - ); + // TODO: Handle different timeline modes (e.g. random order) + const nextTimeline = store.getSuspendableDocumentOrderSuspense( + nextUniqueSuspendersOnly, + ); suspenseTreeDispatch({ type: 'SET_SUSPENSE_TIMELINE', payload: [nextTimeline, null, nextUniqueSuspendersOnly], @@ -101,55 +95,6 @@ function ToggleUniqueSuspenders() { ); } -function SelectRoot() { - const store = useContext(StoreContext); - const {roots, selectedRootID, uniqueSuspendersOnly} = useContext( - SuspenseTreeStateContext, - ); - const treeDispatch = useContext(TreeDispatcherContext); - const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - - function handleChange(event: SyntheticEvent) { - const newRootID = +event.currentTarget.value; - // TODO: scrollIntoView both suspense rects and host instance. - const nextTimeline = store.getSuspendableDocumentOrderSuspense( - newRootID, - uniqueSuspendersOnly, - ); - suspenseTreeDispatch({ - type: 'SET_SUSPENSE_TIMELINE', - payload: [nextTimeline, newRootID, uniqueSuspendersOnly], - }); - if (nextTimeline.length > 0) { - const milestone = nextTimeline[nextTimeline.length - 1]; - treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); - } - } - return ( - roots.length > 0 && ( - - ) - ); -} - function ToggleTreeList({ dispatch, state, @@ -240,6 +185,18 @@ function SuspenseTab(_: {}) { treeListHorizontalFraction, } = state; + const {inspectedElementID} = useContext(TreeStateContext); + const {timeline} = useContext(SuspenseTreeStateContext); + const treeDispatch = useContext(TreeDispatcherContext); + useLayoutEffect(() => { + // If the inspected element is still null and we've loaded a timeline, we can set the initial selection. + // TODO: This tab should use its own source of truth instead so we only show suspense boundaries. + if (inspectedElementID === null && timeline.length > 0) { + const milestone = timeline[timeline.length - 1]; + treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); + } + }, [timeline, inspectedElementID]); + useLayoutEffect(() => { const wrapperElement = wrapperTreeRef.current; @@ -427,7 +384,6 @@ function SuspenseTab(_: {}) {
    -
    {!hideSettings && } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js index b7340da915..30ca21476f 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js @@ -9,7 +9,7 @@ import * as React from 'react'; import {useContext, useEffect, useRef} from 'react'; -import {BridgeContext, StoreContext} from '../context'; +import {BridgeContext} from '../context'; import {TreeDispatcherContext} from '../Components/TreeContext'; import {useHighlightHostInstance, useScrollToHostInstance} from '../hooks'; import { @@ -23,20 +23,15 @@ import ButtonIcon from '../ButtonIcon'; function SuspenseTimelineInput() { const bridge = useContext(BridgeContext); - const store = useContext(StoreContext); const treeDispatch = useContext(TreeDispatcherContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); const {highlightHostInstance, clearHighlightHostInstance} = useHighlightHostInstance(); const scrollToHostInstance = useScrollToHostInstance(); - const { - selectedRootID: rootID, - timeline, - timelineIndex, - hoveredTimelineIndex, - playing, - } = useContext(SuspenseTreeStateContext); + const {timeline, timelineIndex, hoveredTimelineIndex, playing} = useContext( + SuspenseTreeStateContext, + ); const min = 0; const max = timeline.length > 0 ? timeline.length - 1 : 0; @@ -112,24 +107,12 @@ function SuspenseTimelineInput() { // For now we just exclude it from deps since we don't lint those anyway. function changeTimelineIndex(newIndex: number) { // Synchronize timeline index with what is resuspended. - if (rootID === null) { - return; - } - const rendererID = store.getRendererIDForElement(rootID); - if (rendererID === null) { - console.error( - `No renderer ID found for root element ${rootID} in suspense timeline.`, - ); - return; - } // We suspend everything after the current selection. The root isn't showing // anything suspended in the root. The step after that should have one less // thing suspended. I.e. the first suspense boundary should be unsuspended // when it's selected. This also lets you show everything in the last step. const suspendedSet = timeline.slice(timelineIndex + 1); bridge.send('overrideSuspenseMilestone', { - rendererID, - rootID, suspendedSet, }); if (isInitialMount.current) { @@ -164,20 +147,6 @@ function SuspenseTimelineInput() { }; }, [playing]); - if (rootID === null) { - return ( -
    No root selected.
    - ); - } - - if (!store.supportsTogglingSuspense(rootID)) { - return ( -
    - Can't step through Suspense in production apps. -
    - ); - } - if (timeline.length === 0) { return (
    @@ -226,10 +195,9 @@ function SuspenseTimelineInput() { } export default function SuspenseTimeline(): React$Node { - const {selectedRootID} = useContext(SuspenseTreeStateContext); return (
    - +
    ); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js index 151f454eb5..60235e09f3 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js @@ -7,10 +7,7 @@ * @flow */ import type {ReactContext} from 'shared/ReactTypes'; -import type { - Element, - SuspenseNode, -} from 'react-devtools-shared/src/frontend/types'; +import type {SuspenseNode} from 'react-devtools-shared/src/frontend/types'; import type Store from '../../store'; import * as React from 'react'; @@ -27,13 +24,13 @@ import {StoreContext} from '../context'; export type SuspenseTreeState = { lineage: $ReadOnlyArray | null, roots: $ReadOnlyArray, - selectedRootID: SuspenseNode['id'] | null, selectedSuspenseID: SuspenseNode['id'] | null, timeline: $ReadOnlyArray, timelineIndex: number | -1, hoveredTimelineIndex: number | -1, uniqueSuspendersOnly: boolean, playing: boolean, + autoSelect: boolean, }; type ACTION_SUSPENSE_TREE_MUTATION = { @@ -107,60 +104,28 @@ type Props = { children: React$Node, }; -function getDefaultRootID(store: Store): Element['id'] | null { - const designatedRootID = store.roots.find(rootID => { - const suspense = store.getSuspenseByID(rootID); - return ( - store.supportsTogglingSuspense(rootID) && - suspense !== null && - suspense.children.length > 1 - ); - }); - - return designatedRootID === undefined ? null : designatedRootID; -} - function getInitialState(store: Store): SuspenseTreeState { - let initialState: SuspenseTreeState; const uniqueSuspendersOnly = true; - const selectedRootID = getDefaultRootID(store); - // TODO: Default to nearest from inspected - if (selectedRootID === null) { - initialState = { - selectedSuspenseID: null, - lineage: null, - roots: store.roots, - selectedRootID, - timeline: [], - timelineIndex: -1, - hoveredTimelineIndex: -1, - uniqueSuspendersOnly, - playing: false, - }; - } else { - const timeline = store.getSuspendableDocumentOrderSuspense( - selectedRootID, - uniqueSuspendersOnly, - ); - const timelineIndex = timeline.length - 1; - const selectedSuspenseID = - timelineIndex === -1 ? null : timeline[timelineIndex]; - const lineage = - selectedSuspenseID !== null - ? store.getSuspenseLineage(selectedSuspenseID) - : []; - initialState = { - selectedSuspenseID, - lineage, - roots: store.roots, - selectedRootID, - timeline, - timelineIndex, - hoveredTimelineIndex: -1, - uniqueSuspendersOnly, - playing: false, - }; - } + const timeline = + store.getSuspendableDocumentOrderSuspense(uniqueSuspendersOnly); + const timelineIndex = timeline.length - 1; + const selectedSuspenseID = + timelineIndex === -1 ? null : timeline[timelineIndex]; + const lineage = + selectedSuspenseID !== null + ? store.getSuspenseLineage(selectedSuspenseID) + : []; + const initialState: SuspenseTreeState = { + selectedSuspenseID, + lineage, + roots: store.roots, + timeline, + timelineIndex, + hoveredTimelineIndex: -1, + uniqueSuspendersOnly, + playing: false, + autoSelect: true, + }; return initialState; } @@ -209,29 +174,19 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedTimelineID = removedIDs.get(selectedTimelineID); } - let nextRootID = state.selectedRootID; - if (selectedTimelineID !== null && selectedTimelineID !== 0) { - nextRootID = - store.getSuspenseRootIDForSuspense(selectedTimelineID); - } - if (nextRootID === null) { - nextRootID = getDefaultRootID(store); - } - - const nextTimeline = - nextRootID === null - ? [] - : // TODO: Handle different timeline modes (e.g. random order) - store.getSuspendableDocumentOrderSuspense( - nextRootID, - state.uniqueSuspendersOnly, - ); + // TODO: Handle different timeline modes (e.g. random order) + const nextTimeline = store.getSuspendableDocumentOrderSuspense( + state.uniqueSuspendersOnly, + ); let nextTimelineIndex = selectedTimelineID === null || nextTimeline.length === 0 ? -1 : nextTimeline.indexOf(selectedTimelineID); - if (nextTimeline.length > 0 && nextTimelineIndex === -1) { + if ( + nextTimeline.length > 0 && + (nextTimelineIndex === -1 || state.autoSelect) + ) { nextTimelineIndex = nextTimeline.length - 1; selectedSuspenseID = nextTimeline[nextTimelineIndex]; } @@ -250,7 +205,6 @@ function SuspenseTreeContextController({children}: Props): React.Node { ...state, lineage: nextLineage, roots: store.roots, - selectedRootID: nextRootID, selectedSuspenseID, timeline: nextTimeline, timelineIndex: nextTimelineIndex, @@ -258,28 +212,24 @@ function SuspenseTreeContextController({children}: Props): React.Node { } case 'SELECT_SUSPENSE_BY_ID': { const selectedSuspenseID = action.payload; - const selectedRootID = - store.getSuspenseRootIDForSuspense(selectedSuspenseID); return { ...state, selectedSuspenseID, - selectedRootID, playing: false, // pause + autoSelect: false, }; } case 'SET_SUSPENSE_LINEAGE': { const suspenseID = action.payload; const lineage = store.getSuspenseLineage(suspenseID); - const selectedRootID = - store.getSuspenseRootIDForSuspense(suspenseID); return { ...state, lineage, selectedSuspenseID: suspenseID, - selectedRootID, playing: false, // pause + autoSelect: false, }; } case 'SET_SUSPENSE_TIMELINE': { @@ -316,8 +266,6 @@ function SuspenseTreeContextController({children}: Props): React.Node { ...state, selectedSuspenseID: nextSelectedSuspenseID, lineage: nextLineage, - selectedRootID: - nextRootID === null ? state.selectedRootID : nextRootID, timeline: nextTimeline, timelineIndex: nextMilestoneIndex, uniqueSuspendersOnly: nextUniqueSuspendersOnly, @@ -336,6 +284,7 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedSuspenseID: nextSelectedSuspenseID, timelineIndex: nextTimelineIndex, playing: false, // pause + autoSelect: false, }; } case 'SUSPENSE_SKIP_TIMELINE_INDEX': { @@ -358,6 +307,7 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedSuspenseID: nextSelectedSuspenseID, timelineIndex: nextTimelineIndex, playing: false, // pause + autoSelect: false, }; } case 'SUSPENSE_PLAY_PAUSE': { @@ -384,6 +334,7 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedSuspenseID: nextSelectedSuspenseID, timelineIndex: nextTimelineIndex, playing: mode === 'toggle' ? !state.playing : mode === 'play', + autoSelect: false, }; } case 'SUSPENSE_PLAY_TICK': { @@ -440,6 +391,7 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedSuspenseID: nextSelectedSuspenseID, timelineIndex: nextTimelineIndex, playing: false, // pause + autoSelect: false, }; } case 'HOVER_TIMELINE_FOR_ID': { diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index a4ed2da526..2984c2fbfc 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -353,20 +353,44 @@ export function useHighlightHostInstance(): { const highlightHostInstance = useCallback( (id: number, scrollIntoView?: boolean = false) => { const element = store.getElementByID(id); - const rendererID = store.getRendererIDForElement(id); - if (element !== null && rendererID !== null) { + if (element !== null) { + const isRoot = element.parentID === 0; let displayName = element.displayName; if (displayName !== null && element.nameProp !== null) { displayName += ` name="${element.nameProp}"`; } - bridge.send('highlightHostInstance', { - displayName, - hideAfterTimeout: false, - id, - openBuiltinElementsPanel: false, - rendererID, - scrollIntoView: scrollIntoView, - }); + if (isRoot) { + // Inspect screen + const elements: Array<{rendererID: number, id: number}> = []; + + for (let i = 0; i < store.roots.length; i++) { + const rootID = store.roots[i]; + const rendererID = store.getRendererIDForElement(rootID); + if (rendererID === null) { + continue; + } + elements.push({rendererID, id: rootID}); + } + + bridge.send('highlightHostInstances', { + displayName, + hideAfterTimeout: false, + elements, + scrollIntoView: scrollIntoView, + }); + } else { + const rendererID = store.getRendererIDForElement(id); + if (rendererID !== null) { + bridge.send('highlightHostInstance', { + displayName, + hideAfterTimeout: false, + id, + openBuiltinElementsPanel: false, + rendererID, + scrollIntoView: scrollIntoView, + }); + } + } } }, [store, bridge], diff --git a/packages/react-devtools-shared/src/inspectedElementMutableSource.js b/packages/react-devtools-shared/src/inspectedElementMutableSource.js index ae90bd021b..d967109163 100644 --- a/packages/react-devtools-shared/src/inspectedElementMutableSource.js +++ b/packages/react-devtools-shared/src/inspectedElementMutableSource.js @@ -12,6 +12,7 @@ import { convertInspectedElementBackendToFrontend, hydrateHelper, inspectElement as inspectElementAPI, + inspectScreen as inspectScreenAPI, } from 'react-devtools-shared/src/backendAPI'; import {fillInPath} from 'react-devtools-shared/src/hydration'; @@ -57,21 +58,31 @@ export function inspectElement( rendererID: number, shouldListenToPauseEvents: boolean = false, ): Promise { - const {id} = element; + const {id, parentID} = element; // This could indicate that the DevTools UI has been closed and reopened. // The in-memory cache will be clear but the backend still thinks we have cached data. // In this case, we need to tell it to resend the full data. const forceFullData = !inspectedElementCache.has(id); + const isRoot = parentID === 0; + const promisedElement = isRoot + ? inspectScreenAPI( + bridge, + forceFullData, + id, + path, + shouldListenToPauseEvents, + ) + : inspectElementAPI( + bridge, + forceFullData, + id, + path, + rendererID, + shouldListenToPauseEvents, + ); - return inspectElementAPI( - bridge, - forceFullData, - id, - path, - rendererID, - shouldListenToPauseEvents, - ).then((data: any) => { + return promisedElement.then((data: any) => { const {type} = data; let inspectedElement; diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index bc8e768450..007db77f22 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -262,7 +262,6 @@ export function printOperationsArray(operations: Array) { i++; // supportsProfiling i++; // supportsStrictMode i++; // hasOwnerMetadata - i++; // supportsTogglingSuspense } else { const parentID = ((operations[i]: any): number); i++; diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index d9b604c175..b988bd72ca 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -10832,4 +10832,56 @@ Unfortunately that previous paragraph wasn't quite long enough so I'll continue , ); }); + + it('not error when a suspended fallback segment directly inside another Suspense is abandoned', async () => { + function SuspendForever() { + React.use(new Promise(() => {})); + } + + let resolve = () => {}; + const suspendPromise = new Promise(r => { + resolve = r; + }); + function Suspend() { + return React.use(suspendPromise); + } + + function App() { + return ( + + + + }> + hello world + + + + + + + + ); + } + + await act(async () => { + const {pipe} = renderToPipeableStream(, { + onError() {}, + }); + pipe(writable); + }); + + await act(() => { + resolve('!'); + }); + + expect(getVisibleChildren(document)).toEqual( + + + + hello world + ! + + , + ); + }); }); diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 40ff26ff89..0f334eea86 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -645,6 +645,10 @@ export type FragmentInstanceType = { observeUsing: (observer: IntersectionObserver) => void, unobserveUsing: (observer: IntersectionObserver) => void, compareDocumentPosition: (otherNode: PublicInstance) => number, + getRootNode(getRootNodeOptions?: { + composed: boolean, + }): Node | FragmentInstanceType, + getClientRects: () => Array, }; function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { @@ -754,6 +758,42 @@ function collectChildren(child: Fiber, collection: Array): boolean { return false; } +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getRootNode = function ( + this: FragmentInstanceType, + getRootNodeOptions?: {composed: boolean}, +): Node | FragmentInstanceType { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { + return this; + } + const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber); + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + const rootNode = (parentHostInstance.getRootNode(getRootNodeOptions): Node); + return rootNode; +}; + +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getClientRects = function ( + this: FragmentInstanceType, +): Array { + const rects: Array = []; + traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects); + return rects; +}; +function collectClientRects(child: Fiber, rects: Array): boolean { + const instance = getPublicInstanceFromHostFiber(child); + + // getBoundingClientRect is available on Fabric instances while getClientRects is not. + // This should work as a substitute in this case because the only equivalent of a multi-rect + // element in RN would be a nested Text component. + // Since we only use top-level nodes here, we can assume that getBoundingClientRect is sufficient. + // $FlowFixMe[method-unbinding] + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + rects.push(instance.getBoundingClientRect()); + return false; +} + export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { diff --git a/packages/react-reconciler/src/__tests__/ActivityReactServer-test.js b/packages/react-reconciler/src/__tests__/ActivityReactServer-test.js new file mode 100644 index 0000000000..93f4e5683b --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ActivityReactServer-test.js @@ -0,0 +1,65 @@ +/** + * 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. + * + * @emails react-core + * @jest-environment node + */ + +'use strict'; + +let act; +let Activity; +let React; +let ReactServer; +let ReactNoop; +let ReactNoopFlightClient; +let ReactNoopFlightServer; + +describe('ActivityReactServer', () => { + beforeEach(() => { + jest.resetModules(); + jest.mock('react', () => require('react/react.react-server')); + ReactServer = require('react'); + Activity = ReactServer.Activity; + ReactNoopFlightServer = require('react-noop-renderer/flight-server'); + + jest.resetModules(); + __unmockReact(); + React = require('react'); + ReactNoopFlightClient = require('react-noop-renderer/flight-client'); + ReactNoop = require('react-noop-renderer'); + const InternalTestUtils = require('internal-test-utils'); + act = InternalTestUtils.act; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('can be rendered in React Server', async () => { + function App() { + return ReactServer.createElement( + Activity, + {mode: 'hidden'}, + ReactServer.createElement('div', null, 'Hello, Dave!'), + ); + } + + const transport = ReactNoopFlightServer.render( + ReactServer.createElement(App, null), + ); + + await act(async () => { + const app = await ReactNoopFlightClient.read(transport); + + ReactNoop.render(app); + }); + + expect(ReactNoop).toMatchRenderedOutput( + , + ); + }); +}); diff --git a/packages/react-reconciler/src/__tests__/ViewTransitionReactServer-test.js b/packages/react-reconciler/src/__tests__/ViewTransitionReactServer-test.js new file mode 100644 index 0000000000..8f1104a128 --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ViewTransitionReactServer-test.js @@ -0,0 +1,64 @@ +/** + * 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. + * + * @emails react-core + * @jest-environment node + */ + +'use strict'; + +let act; +let ViewTransition; +let React; +let ReactServer; +let ReactNoop; +let ReactNoopFlightClient; +let ReactNoopFlightServer; + +describe('ViewTransitionReactServer', () => { + beforeEach(() => { + jest.resetModules(); + jest.mock('react', () => require('react/react.react-server')); + ReactServer = require('react'); + ViewTransition = ReactServer.unstable_ViewTransition; + ReactNoopFlightServer = require('react-noop-renderer/flight-server'); + + jest.resetModules(); + __unmockReact(); + React = require('react'); + ReactNoopFlightClient = require('react-noop-renderer/flight-client'); + ReactNoop = require('react-noop-renderer'); + const InternalTestUtils = require('internal-test-utils'); + act = InternalTestUtils.act; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + // @gate enableViewTransition || fb + it('can be rendered in React Server', async () => { + function App() { + return ReactServer.createElement( + ViewTransition, + {}, + ReactServer.createElement('div', null, 'Hello, Dave!'), + ); + } + + const transport = ReactNoopFlightServer.render( + ReactServer.createElement(App, null), + ); + + await act(async () => { + const app = await ReactNoopFlightClient.read(transport); + + ReactNoop.render(app); + }); + + expect(ReactNoop).toMatchRenderedOutput(
    Hello, Dave!
    ); + }); +}); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index b8184a1983..07408d64e8 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -342,7 +342,7 @@ type Segment = { // The context that this segment was created in. parentFormatContext: FormatContext, // If this segment represents a fallback, this is the content that will replace that fallback. - +boundary: null | SuspenseBoundary, + boundary: null | SuspenseBoundary, // used to discern when text separator boundaries are needed lastPushedText: boolean, textEmbedded: boolean, @@ -5681,6 +5681,10 @@ function flushSegment( return flushSubtree(request, destination, segment, hoistableState); } + // We're going to write the boundary. We don't need to maintain this reference since + // we might reflush this segment at a later time (if it aborts and we inlined) but + // we don't want to reflush the boundary + segment.boundary = null; boundary.parentFlushed = true; // This segment is a Suspense boundary. We need to decide whether to // emit the content or the fallback now. @@ -5952,7 +5956,7 @@ function flushPartiallyCompletedSegment( segment: Segment, ): boolean { if (segment.status === FLUSHED) { - // We've already flushed this inline. + // We've already flushed this inline return true; } diff --git a/packages/react/src/ReactServer.experimental.development.js b/packages/react/src/ReactServer.experimental.development.js index 082eb58cca..9e9417677b 100644 --- a/packages/react/src/ReactServer.experimental.development.js +++ b/packages/react/src/ReactServer.experimental.development.js @@ -58,6 +58,7 @@ export { export { Children, + REACT_ACTIVITY_TYPE as Activity, REACT_FRAGMENT_TYPE as Fragment, REACT_PROFILER_TYPE as Profiler, REACT_STRICT_MODE_TYPE as StrictMode, @@ -83,6 +84,5 @@ export { // Experimental REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList, REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition, - REACT_ACTIVITY_TYPE as unstable_Activity, captureOwnerStack, // DEV-only }; diff --git a/packages/react/src/ReactServer.experimental.js b/packages/react/src/ReactServer.experimental.js index 4fe83248e2..e380789d71 100644 --- a/packages/react/src/ReactServer.experimental.js +++ b/packages/react/src/ReactServer.experimental.js @@ -57,6 +57,7 @@ export { export { Children, + REACT_ACTIVITY_TYPE as Activity, REACT_FRAGMENT_TYPE as Fragment, REACT_PROFILER_TYPE as Profiler, REACT_STRICT_MODE_TYPE as StrictMode, @@ -82,5 +83,4 @@ export { // Experimental REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList, REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition, - REACT_ACTIVITY_TYPE as Activity, }; diff --git a/packages/react/src/ReactServer.fb.js b/packages/react/src/ReactServer.fb.js index 998a786798..fe6089fc16 100644 --- a/packages/react/src/ReactServer.fb.js +++ b/packages/react/src/ReactServer.fb.js @@ -12,10 +12,13 @@ export {default as __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRA import {forEach, map, count, toArray, only} from './ReactChildren'; import {captureOwnerStack as captureOwnerStackImpl} from './ReactOwnerStack'; import { + REACT_ACTIVITY_TYPE, REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, + REACT_SUSPENSE_LIST_TYPE, + REACT_VIEW_TRANSITION_TYPE, } from 'shared/ReactSymbols'; import { cloneElement, @@ -45,6 +48,7 @@ if (__DEV__) { export { Children, + REACT_ACTIVITY_TYPE as Activity, REACT_FRAGMENT_TYPE as Fragment, REACT_PROFILER_TYPE as Profiler, REACT_STRICT_MODE_TYPE as StrictMode, @@ -65,4 +69,7 @@ export { useMemo, version, captureOwnerStack, // DEV-only + // Experimental + REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList, + REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition, }; diff --git a/packages/react/src/ReactServer.js b/packages/react/src/ReactServer.js index be5cd22d91..f218074dee 100644 --- a/packages/react/src/ReactServer.js +++ b/packages/react/src/ReactServer.js @@ -11,6 +11,7 @@ export {default as __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRA import {forEach, map, count, toArray, only} from './ReactChildren'; import { + REACT_ACTIVITY_TYPE, REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, @@ -40,6 +41,7 @@ const Children = { export { Children, + REACT_ACTIVITY_TYPE as Activity, REACT_FRAGMENT_TYPE as Fragment, REACT_PROFILER_TYPE as Profiler, REACT_STRICT_MODE_TYPE as StrictMode,