Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee90fab4a3 | ||
|
|
e7984651e4 | ||
|
|
5f2b571878 | ||
|
|
56e846921d | ||
|
|
19b71673b1 | ||
|
|
73507ec457 | ||
|
|
03a62b20fd | ||
|
|
b9ec735de2 | ||
|
|
47905a7950 | ||
|
|
7b971c0a55 | ||
|
|
83ea655a0b | ||
|
|
026abeaa5f | ||
|
|
d7215b4970 | ||
|
|
e2ce64acb9 | ||
|
|
34b1567427 | ||
|
|
b467c6e949 | ||
|
|
93d4458fdc | ||
|
|
1d68bce19c | ||
|
|
ead92181bd | ||
|
|
d44659744f | ||
|
|
8454a32f3c | ||
|
|
06fcc8f380 | ||
|
|
91e5c3daf1 | ||
|
|
4b3e662e4c | ||
|
|
3e1b34dc51 | ||
|
|
7568e71854 | ||
|
|
9724e3e66e | ||
|
|
848e0e3a4f | ||
|
|
5c15c1cd34 | ||
|
|
69b4cb8df4 | ||
|
|
a664f5f2ee | ||
|
|
1384ea8230 | ||
|
|
3025aa3964 | ||
|
|
a4eb2dfa6f | ||
|
|
6a8c7fb6f1 | ||
|
|
b65e6fc58b | ||
|
|
c786258422 | ||
|
|
1be3ce9996 | ||
|
|
3b2a398106 | ||
|
|
62ff1e61fc | ||
|
|
0e79784702 | ||
|
|
a2329c10ff | ||
|
|
d3f84a433a | ||
|
|
bc2356176b | ||
|
|
4fdf7cf249 | ||
|
|
614a945d9d | ||
|
|
d6eb735938 | ||
|
|
71753ac90a | ||
|
|
f24d3bbc70 | ||
|
|
85c427d822 | ||
|
|
02bd4458f7 | ||
|
|
0eebd37041 | ||
|
|
74dee8ef64 | ||
|
|
e866b1d1e9 | ||
|
|
19f65ff179 | ||
|
|
26b177bc5e | ||
|
|
056a586928 | ||
|
|
5cc3d49f72 | ||
|
|
289f070d64 | ||
|
|
6a8a8ef326 | ||
|
|
f89ed71ddf | ||
|
|
7d9f876cbc | ||
|
|
df3562dc7f | ||
|
|
b56907db51 | ||
|
|
c825f03067 | ||
|
|
2e68dc76a4 | ||
|
|
ced705d756 | ||
|
|
70b52beca6 | ||
|
|
4a28227960 | ||
|
|
e4a27db283 |
11
.github/workflows/compiler_prereleases.yml
vendored
11
.github/workflows/compiler_prereleases.yml
vendored
@@ -19,6 +19,9 @@ on:
|
||||
tag_version:
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
required: false
|
||||
type: boolean
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
@@ -55,7 +58,13 @@ jobs:
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Publish packages to npm
|
||||
- if: inputs.dry_run == true
|
||||
name: Publish packages to npm (dry run)
|
||||
run: |
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --frfr --debug --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
|
||||
- if: inputs.dry_run != true
|
||||
name: Publish packages to npm
|
||||
run: |
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
|
||||
|
||||
@@ -17,6 +17,9 @@ on:
|
||||
tag_version:
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -33,5 +36,6 @@ jobs:
|
||||
dist_tag: ${{ inputs.dist_tag }}
|
||||
version_name: ${{ inputs.version_name }}
|
||||
tag_version: ${{ inputs.tag_version }}
|
||||
dry_run: ${{ inputs.dry_run }}
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -19,5 +19,6 @@ jobs:
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
version_name: '0.0.0'
|
||||
dry_run: false
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
4
.github/workflows/runtime_prereleases.yml
vendored
4
.github/workflows/runtime_prereleases.yml
vendored
@@ -82,7 +82,6 @@ jobs:
|
||||
run: |
|
||||
scripts/release/publish.js \
|
||||
--ci \
|
||||
--skipTests \
|
||||
--tags=${{ inputs.dist_tag }} \
|
||||
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
@@ -91,11 +90,10 @@ jobs:
|
||||
run: |
|
||||
scripts/release/publish.js \
|
||||
--ci \
|
||||
--skipTests \
|
||||
--tags=${{ inputs.dist_tag }} \
|
||||
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- if: '${{ !(inputs.skip_packages && inputs.only_packages) }}'
|
||||
- if: '${{ !inputs.skip_packages && !inputs.only_packages }}'
|
||||
name: 'Publish all packages'
|
||||
run: |
|
||||
scripts/release/publish.js \
|
||||
|
||||
@@ -33,7 +33,7 @@ const canaryChannelLabel = 'canary';
|
||||
const rcNumber = 0;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '6.2.0',
|
||||
'eslint-plugin-react-hooks': '7.0.0',
|
||||
'jest-react': '0.18.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
|
||||
@@ -314,6 +314,36 @@ test('disableMemoizationForDebugging flag works as expected', async ({
|
||||
expect(output).toMatchSnapshot('disableMemoizationForDebugging-output.txt');
|
||||
});
|
||||
|
||||
test('error is displayed when source has syntax error', async ({page}) => {
|
||||
const syntaxErrorSource = `function TestComponent(props) {
|
||||
const oops = props.
|
||||
return (
|
||||
<>{oops}</>
|
||||
);
|
||||
}`;
|
||||
const store: Store = {
|
||||
source: syntaxErrorSource,
|
||||
config: defaultConfig,
|
||||
showInternals: false,
|
||||
};
|
||||
const hash = encodeStore(store);
|
||||
await page.goto(`/#${hash}`);
|
||||
await page.waitForFunction(isMonacoLoaded);
|
||||
await expandConfigs(page);
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/08-source-syntax-error.png',
|
||||
});
|
||||
|
||||
const text =
|
||||
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
|
||||
const output = text.join('');
|
||||
|
||||
expect(output.replace(/\s+/g, ' ')).toContain(
|
||||
'Expected identifier to be defined before being used',
|
||||
);
|
||||
});
|
||||
|
||||
TEST_CASE_INPUTS.forEach((t, idx) =>
|
||||
test(`playground compiles: ${t.name}`, async ({page}) => {
|
||||
const store: Store = {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import {Resizable} from 're-resizable';
|
||||
import React, {
|
||||
useCallback,
|
||||
useId,
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
@@ -23,7 +22,6 @@ export default function AccordionWindow(props: {
|
||||
tabsOpen: Set<string>;
|
||||
setTabsOpen: (newTab: Set<string>) => void;
|
||||
changedPasses: Set<string>;
|
||||
isFailure: boolean;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="flex-1 min-w-[550px] sm:min-w-0">
|
||||
@@ -37,7 +35,6 @@ export default function AccordionWindow(props: {
|
||||
tabsOpen={props.tabsOpen}
|
||||
setTabsOpen={props.setTabsOpen}
|
||||
hasChanged={props.changedPasses.has(name)}
|
||||
isFailure={props.isFailure}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -52,7 +49,6 @@ function AccordionWindowItem({
|
||||
tabsOpen,
|
||||
setTabsOpen,
|
||||
hasChanged,
|
||||
isFailure,
|
||||
}: {
|
||||
name: string;
|
||||
tabs: TabsRecord;
|
||||
@@ -62,11 +58,11 @@ function AccordionWindowItem({
|
||||
isFailure: boolean;
|
||||
}): React.ReactElement {
|
||||
const id = useId();
|
||||
const isShow = isFailure ? name === 'Output' : tabsOpen.has(name);
|
||||
const isShow = tabsOpen.has(name);
|
||||
|
||||
const transitionName = `accordion-window-item-${id}`;
|
||||
|
||||
const toggleTabs = () => {
|
||||
const toggleTabs = (): void => {
|
||||
startTransition(() => {
|
||||
addTransitionType(EXPAND_ACCORDION_TRANSITION);
|
||||
const nextState = new Set(tabsOpen);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -71,10 +69,10 @@ export default function ConfigEditor({
|
||||
|
||||
function ExpandedEditor({
|
||||
onToggle,
|
||||
appliedOptions,
|
||||
formattedAppliedConfig,
|
||||
}: {
|
||||
onToggle: () => 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 (
|
||||
<ViewTransition
|
||||
update={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}>
|
||||
@@ -158,7 +149,7 @@ function ExpandedEditor({
|
||||
Config Overrides
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex-1 rounded-lg overflow-hidden border border-gray-300">
|
||||
<div className="flex-1 border border-gray-300">
|
||||
<MonacoEditor
|
||||
path={'config.ts'}
|
||||
language={'typescript'}
|
||||
@@ -167,16 +158,7 @@ function ExpandedEditor({
|
||||
onChange={handleChange}
|
||||
loading={''}
|
||||
className="monaco-editor-config"
|
||||
options={{
|
||||
...monacoOptions,
|
||||
lineNumbers: 'off',
|
||||
renderLineHighlight: 'none',
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
fontSize: 12,
|
||||
scrollBeyondLastLine: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
options={monacoConfigOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,23 +168,16 @@ function ExpandedEditor({
|
||||
Applied Configs
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex-1 rounded-lg overflow-hidden border border-gray-300">
|
||||
<div className="flex-1 border border-gray-300">
|
||||
<MonacoEditor
|
||||
path={'applied-config.js'}
|
||||
language={'javascript'}
|
||||
value={formattedAppliedOptions}
|
||||
value={formattedAppliedConfig}
|
||||
loading={''}
|
||||
className="monaco-editor-applied-config"
|
||||
options={{
|
||||
...monacoOptions,
|
||||
lineNumbers: 'off',
|
||||
renderLineHighlight: 'none',
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
fontSize: 12,
|
||||
scrollBeyondLastLine: false,
|
||||
...monacoConfigOptions,
|
||||
readOnly: true,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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<t.File> {
|
||||
// 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<t.File>;
|
||||
}
|
||||
}
|
||||
|
||||
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<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const error = new CompilerError();
|
||||
const otherErrors: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
|
||||
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<CompilerErrorDetail | CompilerDiagnostic>;
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="relative flex top-14">
|
||||
<div className="flex-shrink-0">
|
||||
<ConfigEditor appliedOptions={appliedOptions} />
|
||||
<ConfigEditor formattedAppliedConfig={formattedAppliedConfig} />
|
||||
</div>
|
||||
<div className="flex flex-1 min-w-0">
|
||||
<Input language={language} errors={errors} />
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
useState,
|
||||
Suspense,
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
import AccordionWindow from '../AccordionWindow';
|
||||
import TabbedWindow from '../TabbedWindow';
|
||||
@@ -35,6 +37,7 @@ import {BabelFileResult} from '@babel/core';
|
||||
import {
|
||||
CONFIG_PANEL_TRANSITION,
|
||||
TOGGLE_INTERNALS_TRANSITION,
|
||||
EXPAND_ACCORDION_TRANSITION,
|
||||
} from '../../lib/transitionTypes';
|
||||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
@@ -265,8 +268,22 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
|
||||
* Update the active tab back to the output or errors tab when the compilation state
|
||||
* changes between success/failure.
|
||||
*/
|
||||
|
||||
const [previousOutputKind, setPreviousOutputKind] = useState(
|
||||
compilerOutput.kind,
|
||||
);
|
||||
const isFailure = compilerOutput.kind !== 'ok';
|
||||
|
||||
if (compilerOutput.kind !== previousOutputKind) {
|
||||
setPreviousOutputKind(compilerOutput.kind);
|
||||
if (isFailure) {
|
||||
startTransition(() => {
|
||||
addTransitionType(EXPAND_ACCORDION_TRANSITION);
|
||||
setTabsOpen(prev => new Set(prev).add('Output'));
|
||||
setActiveTab('Output');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
|
||||
let lastResult: string = '';
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
@@ -295,8 +312,6 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
// Display the Output tab on compilation failure
|
||||
activeTabOverride={isFailure ? 'Output' : undefined}
|
||||
/>
|
||||
</ViewTransition>
|
||||
);
|
||||
@@ -315,7 +330,6 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
|
||||
tabsOpen={tabsOpen}
|
||||
tabs={tabs}
|
||||
changedPasses={changedPasses}
|
||||
isFailure={isFailure}
|
||||
/>
|
||||
</ViewTransition>
|
||||
);
|
||||
@@ -372,12 +386,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,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -32,3 +32,14 @@ export const monacoOptions: Partial<EditorProps['options']> = {
|
||||
|
||||
tabSize: 2,
|
||||
};
|
||||
|
||||
export const monacoConfigOptions: Partial<EditorProps['options']> = {
|
||||
...monacoOptions,
|
||||
lineNumbers: 'off',
|
||||
renderLineHighlight: 'none',
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
fontSize: 12,
|
||||
scrollBeyondLastLine: false,
|
||||
glyphMargin: false,
|
||||
};
|
||||
|
||||
@@ -17,15 +17,11 @@ export default function TabbedWindow({
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
activeTabOverride,
|
||||
}: {
|
||||
tabs: Map<string, React.ReactNode>;
|
||||
activeTab: string;
|
||||
onTabChange: (tab: string) => void;
|
||||
activeTabOverride?: string;
|
||||
}): React.ReactElement {
|
||||
const currentActiveTab = activeTabOverride ? activeTabOverride : activeTab;
|
||||
|
||||
const id = useId();
|
||||
const transitionName = `tab-highlight-${id}`;
|
||||
|
||||
@@ -41,7 +37,7 @@ export default function TabbedWindow({
|
||||
<div className="flex flex-col h-full max-w-full">
|
||||
<div className="flex p-2 flex-shrink-0">
|
||||
{Array.from(tabs.keys()).map(tab => {
|
||||
const isActive = currentActiveTab === tab;
|
||||
const isActive = activeTab === tab;
|
||||
return (
|
||||
<button
|
||||
key={tab}
|
||||
@@ -77,7 +73,7 @@ export default function TabbedWindow({
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden w-full h-full">
|
||||
{tabs.get(currentActiveTab)}
|
||||
{tabs.get(activeTab)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
308
compiler/apps/playground/lib/compilation.ts
Normal file
308
compiler/apps/playground/lib/compilation.ts
Normal file
@@ -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<t.File> {
|
||||
// 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<t.File>;
|
||||
}
|
||||
}
|
||||
|
||||
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<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const error = new CompilerError();
|
||||
const otherErrors: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
|
||||
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() || !transformOutput) {
|
||||
return [{kind: 'err', results, error}, language, baseOpts];
|
||||
}
|
||||
return [
|
||||
{kind: 'ok', results, transformOutput, errors: error.details},
|
||||
language,
|
||||
baseOpts,
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"react-dom": "0.0.0-experimental-4beb1fd8-20241118",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^2.1.0"
|
||||
"zod": "^3.22.4 || ^4.0.0",
|
||||
"zod-validation-error": "^3.0.3 || ^4.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"./**/@babel/parser": "7.7.4",
|
||||
|
||||
@@ -536,7 +536,8 @@ function printErrorSummary(category: ErrorCategory, message: string): string {
|
||||
case ErrorCategory.StaticComponents:
|
||||
case ErrorCategory.Suppression:
|
||||
case ErrorCategory.Syntax:
|
||||
case ErrorCategory.UseMemo: {
|
||||
case ErrorCategory.UseMemo:
|
||||
case ErrorCategory.VoidUseMemo: {
|
||||
heading = 'Error';
|
||||
break;
|
||||
}
|
||||
@@ -582,6 +583,10 @@ export enum ErrorCategory {
|
||||
* Checking for valid usage of manual memoization
|
||||
*/
|
||||
UseMemo = 'UseMemo',
|
||||
/**
|
||||
* Checking that useMemos always return a value
|
||||
*/
|
||||
VoidUseMemo = 'VoidUseMemo',
|
||||
/**
|
||||
* Checking for higher order functions acting as factories for components/hooks
|
||||
*/
|
||||
@@ -669,6 +674,21 @@ export enum ErrorCategory {
|
||||
FBT = 'FBT',
|
||||
}
|
||||
|
||||
export enum LintRulePreset {
|
||||
/**
|
||||
* Rules that are stable and included in the `recommended` preset.
|
||||
*/
|
||||
Recommended = 'recommended',
|
||||
/**
|
||||
* Rules that are more experimental and only included in the `recommended-latest` preset.
|
||||
*/
|
||||
RecommendedLatest = 'recommended-latest',
|
||||
/**
|
||||
* Rules that are disabled.
|
||||
*/
|
||||
Off = 'off',
|
||||
}
|
||||
|
||||
export type LintRule = {
|
||||
// Stores the category the rule corresponds to, used to filter errors when reporting
|
||||
category: ErrorCategory;
|
||||
@@ -689,15 +709,14 @@ export type LintRule = {
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* If true, this rule will automatically appear in the default, "recommended" ESLint
|
||||
* rule set. Otherwise it will be part of an `allRules` export that developers can
|
||||
* use to opt-in to showing output of all possible rules.
|
||||
* Configures the preset in which the rule is enabled. If 'off', the rule will not be included in
|
||||
* any preset.
|
||||
*
|
||||
* NOTE: not all validations are enabled by default! Setting this flag only affects
|
||||
* whether a given rule is part of the recommended set. The corresponding validation
|
||||
* also should be enabled by default if you want the error to actually show up!
|
||||
*/
|
||||
recommended: boolean;
|
||||
preset: LintRulePreset;
|
||||
};
|
||||
|
||||
const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;
|
||||
@@ -720,7 +739,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'automatic-effect-dependencies',
|
||||
description:
|
||||
'Verifies that automatic effect dependencies are compiled if opted-in',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.CapitalizedCalls: {
|
||||
@@ -730,7 +749,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'capitalized-calls',
|
||||
description:
|
||||
'Validates against calling capitalized functions/methods instead of using JSX',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Config: {
|
||||
@@ -739,7 +758,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'config',
|
||||
description: 'Validates the compiler configuration options',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.EffectDependencies: {
|
||||
@@ -748,7 +767,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'memoized-effect-dependencies',
|
||||
description: 'Validates that effect dependencies are memoized',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.EffectDerivationsOfState: {
|
||||
@@ -758,7 +777,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'no-deriving-state-in-effects',
|
||||
description:
|
||||
'Validates against deriving values from state in an effect',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.EffectSetState: {
|
||||
@@ -768,7 +787,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'set-state-in-effect',
|
||||
description:
|
||||
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.ErrorBoundaries: {
|
||||
@@ -778,7 +797,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'error-boundaries',
|
||||
description:
|
||||
'Validates usage of error boundaries instead of try/catch for errors in child components',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Factories: {
|
||||
@@ -789,7 +808,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
description:
|
||||
'Validates against higher order functions defining nested components or hooks. ' +
|
||||
'Components and hooks should be defined at the module level',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.FBT: {
|
||||
@@ -798,7 +817,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'fbt',
|
||||
description: 'Validates usage of fbt',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Fire: {
|
||||
@@ -807,7 +826,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'fire',
|
||||
description: 'Validates usage of `fire`',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Gating: {
|
||||
@@ -817,7 +836,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'gating',
|
||||
description:
|
||||
'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Globals: {
|
||||
@@ -828,7 +847,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
description:
|
||||
'Validates against assignment/mutation of globals during render, part of ensuring that ' +
|
||||
'[side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Hooks: {
|
||||
@@ -842,7 +861,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
* We need to dedeupe these (moving the remaining bits into the compiler) and then enable
|
||||
* this rule.
|
||||
*/
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Immutability: {
|
||||
@@ -852,7 +871,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'immutability',
|
||||
description:
|
||||
'Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Invariant: {
|
||||
@@ -861,7 +880,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'invariant',
|
||||
description: 'Internal invariants',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.PreserveManualMemo: {
|
||||
@@ -873,7 +892,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
'Validates that existing manual memoized is preserved by the compiler. ' +
|
||||
'React Compiler will only compile components and hooks if its inference ' +
|
||||
'[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Purity: {
|
||||
@@ -883,7 +902,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'purity',
|
||||
description:
|
||||
'Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Refs: {
|
||||
@@ -893,7 +912,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'refs',
|
||||
description:
|
||||
'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.RenderSetState: {
|
||||
@@ -903,7 +922,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'set-state-in-render',
|
||||
description:
|
||||
'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.StaticComponents: {
|
||||
@@ -913,7 +932,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'static-components',
|
||||
description:
|
||||
'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Suppression: {
|
||||
@@ -922,7 +941,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'rule-suppression',
|
||||
description: 'Validates against suppression of other rules',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Syntax: {
|
||||
@@ -931,7 +950,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'syntax',
|
||||
description: 'Validates against invalid syntax',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.Todo: {
|
||||
@@ -940,7 +959,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Hint,
|
||||
name: 'todo',
|
||||
description: 'Unimplemented features',
|
||||
recommended: false,
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.UnsupportedSyntax: {
|
||||
@@ -950,7 +969,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'unsupported-syntax',
|
||||
description:
|
||||
'Validates against syntax that we do not plan to support in React Compiler',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.UseMemo: {
|
||||
@@ -960,7 +979,17 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'use-memo',
|
||||
description:
|
||||
'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.VoidUseMemo: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'void-use-memo',
|
||||
description:
|
||||
'Validates that useMemos always return a value. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
|
||||
preset: LintRulePreset.RecommendedLatest,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.IncompatibleLibrary: {
|
||||
@@ -970,7 +999,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
name: 'incompatible-library',
|
||||
description:
|
||||
'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
|
||||
recommended: true,
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -159,7 +159,7 @@ export const EnvironmentConfigSchema = z.object({
|
||||
* A function that, given the name of a module, can optionally return a description
|
||||
* of that module's type signature.
|
||||
*/
|
||||
moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null),
|
||||
moduleTypeProvider: z.nullable(z.any()).default(null),
|
||||
|
||||
/**
|
||||
* A list of functions which the application compiles as macros, where
|
||||
@@ -249,7 +249,7 @@ export const EnvironmentConfigSchema = z.object({
|
||||
* Allows specifying a function that can populate HIR with type information from
|
||||
* Flow
|
||||
*/
|
||||
flowTypeProvider: z.nullable(z.function().args(z.string())).default(null),
|
||||
flowTypeProvider: z.nullable(z.any()).default(null),
|
||||
|
||||
/**
|
||||
* Enables inference of optional dependency chains. Without this flag
|
||||
@@ -906,6 +906,12 @@ export class Environment {
|
||||
if (moduleTypeProvider == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof moduleTypeProvider !== 'function') {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason: `Expected a function for \`moduleTypeProvider\``,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
const unparsedModuleConfig = moduleTypeProvider(moduleName);
|
||||
if (unparsedModuleConfig != null) {
|
||||
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
|
||||
|
||||
@@ -454,7 +454,7 @@ export function dropManualMemoization(
|
||||
if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
category: ErrorCategory.VoidUseMemo,
|
||||
reason: 'useMemo() callbacks must return a value',
|
||||
description: `This ${
|
||||
manualMemo.loadInstr.value.kind === 'PropertyLoad'
|
||||
|
||||
@@ -393,7 +393,7 @@ function* generateInstructionTypes(
|
||||
shapeId: BuiltInArrayId,
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('parseConfigPragma()', () => {
|
||||
validateHooksUsage: 1,
|
||||
} as any);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage"."`,
|
||||
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Invalid input: expected boolean, received number at "validateHooksUsage"."`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('parseConfigPragma()', () => {
|
||||
],
|
||||
} as any);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`,
|
||||
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: AutodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
// Intentionally don't bind state, this repros a bug where we didn't
|
||||
// infer the type of destructured properties after a hole in the array
|
||||
let [, setState] = useState();
|
||||
setState(1);
|
||||
return props.foo;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Calling setState during render may trigger an infinite loop
|
||||
|
||||
Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
|
||||
|
||||
error.invalid-setState-in-render-unbound-state.ts:5:2
|
||||
3 | // infer the type of destructured properties after a hole in the array
|
||||
4 | let [, setState] = useState();
|
||||
> 5 | setState(1);
|
||||
| ^^^^^^^^ Found setState() in render
|
||||
6 | return props.foo;
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
function Component(props) {
|
||||
// Intentionally don't bind state, this repros a bug where we didn't
|
||||
// infer the type of destructured properties after a hole in the array
|
||||
let [, setState] = useState();
|
||||
setState(1);
|
||||
return props.foo;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function t(props) {
|
||||
let [, setstate] = useState();
|
||||
setstate(1);
|
||||
return props.foo;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: t,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function t(props) {
|
||||
const [, setstate] = useState();
|
||||
setstate(1);
|
||||
return props.foo;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: t,
|
||||
params: ["TodoAdd"],
|
||||
isComponent: "TodoAdd",
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
function t(props) {
|
||||
let [, setstate] = useState();
|
||||
setstate(1);
|
||||
return props.foo;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: t,
|
||||
params: ['TodoAdd'],
|
||||
isComponent: 'TodoAdd',
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback, useTransition} from 'react';
|
||||
|
||||
function useFoo() {
|
||||
const [, /* isPending intentionally not captured */ start] = useTransition();
|
||||
|
||||
return useCallback(() => {
|
||||
start();
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, useTransition } from "react";
|
||||
|
||||
function useFoo() {
|
||||
const $ = _c(1);
|
||||
const [, start] = useTransition();
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = () => {
|
||||
start();
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "[[ function params=0 ]]"
|
||||
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback, useTransition} from 'react';
|
||||
|
||||
function useFoo() {
|
||||
const [, /* isPending intentionally not captured */ start] = useTransition();
|
||||
|
||||
return useCallback(() => {
|
||||
start();
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback, useTransition} from 'react';
|
||||
|
||||
function useFoo() {
|
||||
const [, /* state value intentionally not captured */ setState] = useState();
|
||||
|
||||
return useCallback(() => {
|
||||
setState(x => x + 1);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback, useTransition } from "react";
|
||||
|
||||
function useFoo() {
|
||||
const $ = _c(1);
|
||||
const [, setState] = useState();
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = () => {
|
||||
setState(_temp);
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp(x) {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) useState is not defined
|
||||
@@ -0,0 +1,15 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback, useTransition} from 'react';
|
||||
|
||||
function useFoo() {
|
||||
const [, /* state value intentionally not captured */ setState] = useState();
|
||||
|
||||
return useCallback(() => {
|
||||
setState(x => x + 1);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
@@ -14,6 +14,7 @@ export {
|
||||
ErrorSeverity,
|
||||
ErrorCategory,
|
||||
LintRules,
|
||||
LintRulePreset,
|
||||
type CompilerErrorDetailOptions,
|
||||
type CompilerDiagnosticOptions,
|
||||
type CompilerDiagnosticDetail,
|
||||
|
||||
@@ -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 <div>Hello world</div>
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message: "Unused 'use no forget' directive",
|
||||
suggestions: [
|
||||
{
|
||||
output:
|
||||
// yuck
|
||||
'\nfunction Component() {\n \n return <div>Hello world</div>\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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -10,6 +10,5 @@ module.exports = {
|
||||
plugins: [
|
||||
['@babel/plugin-transform-private-property-in-object', {loose: true}],
|
||||
['@babel/plugin-transform-class-properties', {loose: true}],
|
||||
['@babel/plugin-transform-private-methods', {loose: true}],
|
||||
],
|
||||
};
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/parser": "^7.24.4",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"hermes-parser": "^0.25.1",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^3.0.3"
|
||||
"zod": "^3.22.4 || ^4.0.0",
|
||||
"zod-validation-error": "^3.0.3 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.22.4",
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {Linter, Rule} from 'eslint';
|
||||
import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler';
|
||||
import {
|
||||
ErrorSeverity,
|
||||
LintRulePreset,
|
||||
LintRules,
|
||||
type LintRule,
|
||||
} from 'babel-plugin-react-compiler/src/CompilerError';
|
||||
@@ -150,7 +151,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: rule.description,
|
||||
recommended: rule.recommended,
|
||||
recommended: rule.preset === LintRulePreset.Recommended,
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
@@ -161,69 +162,30 @@ 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,
|
||||
);
|
||||
rule => rule.preset === LintRulePreset.Recommended,
|
||||
).reduce((acc, rule) => {
|
||||
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
|
||||
return acc;
|
||||
}, {} as RulesConfig);
|
||||
|
||||
export const recommendedLatestRules: RulesConfig = LintRules.filter(
|
||||
rule =>
|
||||
rule.preset === LintRulePreset.Recommended ||
|
||||
rule.preset === LintRulePreset.RecommendedLatest,
|
||||
).reduce((acc, rule) => {
|
||||
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
|
||||
return acc;
|
||||
}, {} as RulesConfig);
|
||||
|
||||
export function mapErrorSeverityToESlint(
|
||||
severity: ErrorSeverity,
|
||||
|
||||
@@ -5,20 +5,16 @@
|
||||
* 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';
|
||||
// @ts-expect-error: no types available
|
||||
import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
|
||||
import {File} from '@babel/types';
|
||||
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 +41,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<UnusedOptOutDirective>;
|
||||
events: Array<LoggerEvent>;
|
||||
};
|
||||
|
||||
@@ -87,25 +77,6 @@ function getFlowSuppressions(
|
||||
return results;
|
||||
}
|
||||
|
||||
function filterUnusedOptOutDirectives(
|
||||
directives: ReadonlyArray<Directive>,
|
||||
): Array<UnusedOptOutDirective> {
|
||||
const results: Array<UnusedOptOutDirective> = [];
|
||||
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 +96,6 @@ function runReactCompilerImpl({
|
||||
filename,
|
||||
userOpts,
|
||||
flowSuppressions: [],
|
||||
unusedOptOutDirectives: [],
|
||||
events: [],
|
||||
};
|
||||
const userLogger: Logger | null = options.logger;
|
||||
@@ -173,37 +143,11 @@ function runReactCompilerImpl({
|
||||
filename,
|
||||
highlightCode: false,
|
||||
retainLines: true,
|
||||
plugins: [
|
||||
[PluginProposalPrivateMethods, {loose: true}],
|
||||
[BabelPluginReactCompiler, options],
|
||||
],
|
||||
plugins: [[BabelPluginReactCompiler, options]],
|
||||
sourceType: 'module',
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
});
|
||||
|
||||
if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
|
||||
traverse(babelAST, {
|
||||
FunctionDeclaration(path) {
|
||||
path.node;
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
},
|
||||
ArrowFunctionExpression(path) {
|
||||
if (path.node.body.type === 'BlockStatement') {
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
}
|
||||
},
|
||||
FunctionExpression(path) {
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
/* errors handled by injected logger */
|
||||
}
|
||||
|
||||
@@ -10,13 +10,7 @@ import {defineConfig} from 'tsup';
|
||||
export default defineConfig({
|
||||
entry: ['./src/index.ts'],
|
||||
outDir: './dist',
|
||||
external: [
|
||||
'@babel/core',
|
||||
'@babel/plugin-proposal-private-methods',
|
||||
'hermes-parser',
|
||||
'zod',
|
||||
'zod-validation-error',
|
||||
],
|
||||
external: ['@babel/core', 'hermes-parser', 'zod', 'zod-validation-error'],
|
||||
splitting: false,
|
||||
sourcemap: false,
|
||||
dts: false,
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"fast-glob": "^3.3.2",
|
||||
"ora": "5.4.1",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^3.0.3"
|
||||
"zod": "^3.22.4 || ^4.0.0",
|
||||
"zod-validation-error": "^3.0.3 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"engines": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"html-to-text": "^9.0.5",
|
||||
"prettier": "^3.3.3",
|
||||
"puppeteer": "^24.7.2",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.22.4 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/html-to-text": "^9.0.4",
|
||||
|
||||
@@ -68,7 +68,7 @@ async function main() {
|
||||
.option('tag', {
|
||||
description: 'Tag to publish to npm',
|
||||
type: 'choices',
|
||||
choices: ['experimental', 'beta', 'rc'],
|
||||
choices: ['experimental', 'beta', 'rc', 'latest'],
|
||||
default: 'experimental',
|
||||
})
|
||||
.option('tag-version', {
|
||||
@@ -145,10 +145,15 @@ async function main() {
|
||||
files: {exclude: ['.DS_Store']},
|
||||
});
|
||||
const truncatedHash = hash.slice(0, 7);
|
||||
let newVersion =
|
||||
argv.tagVersion == null || argv.tagVersion === ''
|
||||
? `${argv.versionName}-${argv.tag}`
|
||||
: `${argv.versionName}-${argv.tag}.${argv.tagVersion}`;
|
||||
let newVersion;
|
||||
if (argv.tag === 'latest') {
|
||||
newVersion = argv.versionName;
|
||||
} else {
|
||||
newVersion =
|
||||
argv.tagVersion == null || argv.tagVersion === ''
|
||||
? `${argv.versionName}-${argv.tag}`
|
||||
: `${argv.versionName}-${argv.tag}.${argv.tagVersion}`;
|
||||
}
|
||||
if (argv.tag === 'experimental' || argv.tag === 'beta') {
|
||||
newVersion = `${newVersion}-${truncatedHash}-${dateString}`;
|
||||
}
|
||||
@@ -181,21 +186,9 @@ async function main() {
|
||||
if (otp != null) {
|
||||
opts.push(`--otp=${otp}`);
|
||||
}
|
||||
/**
|
||||
* Typically, the `latest` tag is reserved for stable package versions. Since the the compiler
|
||||
* is still pre-release, until we have a stable release let's only add the
|
||||
* `latest` tag to non-experimental releases.
|
||||
*
|
||||
* `latest` is added by default, so we only override it for experimental releases so that
|
||||
* those don't get the `latest` tag.
|
||||
*
|
||||
* TODO: Update this when we have a stable release.
|
||||
*/
|
||||
if (argv.tag === 'experimental') {
|
||||
opts.push('--tag=experimental');
|
||||
} else {
|
||||
opts.push('--tag=latest');
|
||||
}
|
||||
|
||||
opts.push(`--tag=${argv.tag}`);
|
||||
|
||||
try {
|
||||
await spawnHelper(
|
||||
'npm',
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
lru-cache "^5.1.1"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0":
|
||||
"@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz"
|
||||
integrity sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==
|
||||
@@ -706,14 +706,6 @@
|
||||
"@babel/helper-plugin-utils" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
|
||||
"@babel/plugin-proposal-private-methods@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz"
|
||||
integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
|
||||
dependencies:
|
||||
"@babel/helper-create-class-features-plugin" "^7.18.6"
|
||||
"@babel/helper-plugin-utils" "^7.18.6"
|
||||
|
||||
"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
|
||||
version "7.21.0-placeholder-for-preset-env.2"
|
||||
resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz"
|
||||
@@ -10494,16 +10486,7 @@ string-length@^4.0.1:
|
||||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -10576,14 +10559,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -11360,7 +11336,7 @@ workerpool@^6.5.1:
|
||||
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz"
|
||||
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -11378,15 +11354,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
|
||||
@@ -11538,17 +11505,17 @@ zod-to-json-schema@^3.24.1:
|
||||
resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz"
|
||||
integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==
|
||||
|
||||
zod-validation-error@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz"
|
||||
integrity sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==
|
||||
"zod-validation-error@^3.0.3 || ^4.0.0":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918"
|
||||
integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==
|
||||
|
||||
zod-validation-error@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.0.3.tgz"
|
||||
integrity sha512-cETTrcMq3Ze58vhdR0zD37uJm/694I6mAxcf/ei5bl89cC++fBNxrC2z8lkFze/8hVMPwrbtrwXHR2LB50fpHw==
|
||||
"zod@^3.22.4 || ^4.0.0":
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.11.tgz#4aab62f76cfd45e6c6166519ba31b2ea019f75f5"
|
||||
integrity sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==
|
||||
|
||||
zod@^3.22.4, zod@^3.23.8, zod@^3.24.1:
|
||||
zod@^3.23.8, zod@^3.24.1:
|
||||
version "3.24.3"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz"
|
||||
integrity sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==
|
||||
|
||||
@@ -55,11 +55,11 @@ export default function ScrollIntoViewCase() {
|
||||
const scrollContainerRef = useRef(null);
|
||||
|
||||
const scrollVertical = () => {
|
||||
fragmentRef.current.experimental_scrollIntoView(alignToTop);
|
||||
fragmentRef.current.scrollIntoView(alignToTop);
|
||||
};
|
||||
|
||||
const scrollVerticalNoChildren = () => {
|
||||
noChildRef.current.experimental_scrollIntoView(alignToTop);
|
||||
noChildRef.current.scrollIntoView(alignToTop);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["plugin:react-hooks/recommended-legacy"],
|
||||
"extends": ["plugin:react-hooks/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,3 +141,27 @@ function useHookInLoops() {
|
||||
useHook4();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiler Rules
|
||||
*/
|
||||
// Invalid: component factory
|
||||
function InvalidComponentFactory() {
|
||||
const DynamicComponent = () => <div>Hello</div>;
|
||||
// eslint-disable-next-line react-hooks/static-components
|
||||
return <DynamicComponent />;
|
||||
}
|
||||
|
||||
// Invalid: mutating globals
|
||||
function InvalidGlobals() {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
window.myGlobal = 42;
|
||||
return <div>Done</div>;
|
||||
}
|
||||
|
||||
// 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 <div>{sorted.length}</div>;
|
||||
}
|
||||
|
||||
@@ -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==
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["plugin:react-hooks/recommended-legacy"],
|
||||
"extends": ["plugin:react-hooks/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,3 +141,27 @@ function useHookInLoops() {
|
||||
useHook4();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiler Rules
|
||||
*/
|
||||
// Invalid: component factory
|
||||
function InvalidComponentFactory() {
|
||||
const DynamicComponent = () => <div>Hello</div>;
|
||||
// eslint-disable-next-line react-hooks/static-components
|
||||
return <DynamicComponent />;
|
||||
}
|
||||
|
||||
// Invalid: mutating globals
|
||||
function InvalidGlobals() {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
window.myGlobal = 42;
|
||||
return <div>Done</div>;
|
||||
}
|
||||
|
||||
// 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 <div>{sorted.length}</div>;
|
||||
}
|
||||
|
||||
@@ -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==
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["plugin:react-hooks/recommended-legacy"],
|
||||
"extends": ["plugin:react-hooks/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,3 +141,27 @@ function useHookInLoops() {
|
||||
useHook4();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiler Rules
|
||||
*/
|
||||
// Invalid: component factory
|
||||
function InvalidComponentFactory() {
|
||||
const DynamicComponent = () => <div>Hello</div>;
|
||||
// eslint-disable-next-line react-hooks/static-components
|
||||
return <DynamicComponent />;
|
||||
}
|
||||
|
||||
// Invalid: mutating globals
|
||||
function InvalidGlobals() {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
window.myGlobal = 42;
|
||||
return <div>Done</div>;
|
||||
}
|
||||
|
||||
// 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 <div>{sorted.length}</div>;
|
||||
}
|
||||
|
||||
@@ -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==
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {defineConfig} from 'eslint/config';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
console.log(reactHooks.configs['recommended-latest']);
|
||||
|
||||
export default defineConfig([
|
||||
reactHooks.configs.flat['recommended-latest'],
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
@@ -14,10 +13,6 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended-latest'],
|
||||
rules: {
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
},
|
||||
|
||||
@@ -141,3 +141,27 @@ function useHookInLoops() {
|
||||
useHook4();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiler Rules
|
||||
*/
|
||||
// Invalid: component factory
|
||||
function InvalidComponentFactory() {
|
||||
const DynamicComponent = () => <div>Hello</div>;
|
||||
// eslint-disable-next-line react-hooks/static-components
|
||||
return <DynamicComponent />;
|
||||
}
|
||||
|
||||
// Invalid: mutating globals
|
||||
function InvalidGlobals() {
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
window.myGlobal = 42;
|
||||
return <div>Done</div>;
|
||||
}
|
||||
|
||||
// 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 <div>{sorted.length}</div>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build.mjs && yarn",
|
||||
"lint": "eslint index.js --report-unused-disable-directives"
|
||||
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2022"
|
||||
],
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"target": "es2022",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules",
|
||||
"../node_modules",
|
||||
"../../node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,212 +2,144 @@
|
||||
# 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-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/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"
|
||||
"@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-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-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/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/traverse" "^7.27.1"
|
||||
"@babel/types" "^7.27.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-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/traverse" "^7.25.9"
|
||||
"@babel/types" "^7.25.9"
|
||||
"@babel/helper-module-imports" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
"@babel/traverse" "^7.28.3"
|
||||
|
||||
"@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-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.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.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.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/traverse" "^7.25.9"
|
||||
"@babel/types" "^7.25.9"
|
||||
"@babel/template" "^7.27.2"
|
||||
"@babel/types" "^7.28.4"
|
||||
|
||||
"@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/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/helper-module-imports" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
"@babel/traverse" "^7.25.9"
|
||||
"@babel/types" "^7.28.4"
|
||||
|
||||
"@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/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/types" "^7.25.9"
|
||||
"@babel/code-frame" "^7.27.1"
|
||||
"@babel/parser" "^7.27.2"
|
||||
"@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-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/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/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"
|
||||
"@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.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 +235,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 +256,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 +321,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 +335,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 +394,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 +413,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 +593,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"
|
||||
@@ -661,18 +603,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"
|
||||
@@ -801,10 +731,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 +779,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 +835,12 @@ type-check@^0.4.0, type-check@~0.4.0:
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
|
||||
update-browserslist-db@^1.1.1:
|
||||
typescript@^5.4.3:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
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==
|
||||
@@ -942,12 +877,12 @@ yocto-queue@^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-validation-error@^3.0.3 || ^4.0.0":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918"
|
||||
integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==
|
||||
|
||||
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==
|
||||
"zod@^3.22.4 || ^4.0.0":
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.11.tgz#4aab62f76cfd45e6c6166519ba31b2ea019f75f5"
|
||||
integrity sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
useState,
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
addTransitionType,
|
||||
} from 'react';
|
||||
|
||||
import Chrome from './Chrome.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
addTransitionType,
|
||||
ViewTransition,
|
||||
Activity,
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
## 7.0.0
|
||||
|
||||
This release slims down presets to just 2 configurations (`recommended` and `recommended-latest`), and all compiler rules are enabled by default.
|
||||
|
||||
- **Breaking:** Removed `recommended-latest-legacy` and `flat/recommended` configs. The plugin now provides `recommended` (legacy and flat configs with all recommended rules), and `recommended-latest` (legacy and flat configs with all recommended rules plus new bleeding edge experimental compiler rules). ([@poteto](https://github.com/poteto) in [#34757](https://github.com/facebook/react/pull/34757))
|
||||
|
||||
## 6.1.1
|
||||
|
||||
**Note:** 6.1.0 accidentally allowed use of `recommended` without flat config, causing errors when used with ESLint v9's `defineConfig()` helper. This has been fixed in 6.1.1.
|
||||
|
||||
- Fix `recommended` config for flat config compatibility. The `recommended` config has been converted to flat config format. Non-flat config users should use `recommended-legacy` instead. ([@poteto](https://github.com/poteto) in [#34700](https://github.com/facebook/react/pull/34700))
|
||||
- Add `recommended-latest` and `recommended-latest-legacy` configs that include React Compiler rules. ([@poteto](https://github.com/poteto) in [#34675](https://github.com/facebook/react/pull/34675))
|
||||
- Remove unused `NoUnusedOptOutDirectives` rule. ([@poteto](https://github.com/poteto) in [#34703](https://github.com/facebook/react/pull/34703))
|
||||
- Remove `hermes-parser` and dependency. ([@poteto](https://github.com/poteto) in [#34719](https://github.com/facebook/react/pull/34719))
|
||||
- Remove `@babel/plugin-proposal-private-methods` dependency. ([@ArnaudBarre](https://github.com/ArnaudBarre) and [@josephsavona](https://github.com/josephsavona) in [#34715](https://github.com/facebook/react/pull/34715))
|
||||
- Update for Zod v3/v4 compatibility. ([@kolian](https://github.com/kolvian) and [@josephsavona](https://github.com/josephsavona) in [#34717](https://github.com/facebook/react/pull/34717))
|
||||
|
||||
## 6.1.0
|
||||
|
||||
**Note:** Version 6.0.0 was mistakenly released and immediately deprecated and untagged on npm. This is the first official 6.x major release and includes breaking changes.
|
||||
**Note:** Version 6.0.0 was mistakenly released and immediately deprecated and untagged on npm. This is the first official 6.x major release and includes breaking changes.
|
||||
|
||||
- **Breaking:** Require Node.js 18 or newer. ([@michaelfaith](https://github.com/michaelfaith) in [#32458](https://github.com/facebook/react/pull/32458))
|
||||
- **Breaking:** Flat config is now the default `recommended` preset. Legacy config moved to `recommended-legacy`. ([@michaelfaith](https://github.com/michaelfaith) in [#32457](https://github.com/facebook/react/pull/32457))
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# `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
|
||||
|
||||
**Note: If you're using Create React App, please use `react-scripts` >= 3 instead of adding it directly.**
|
||||
|
||||
Assuming you already have ESLint installed, run:
|
||||
|
||||
```sh
|
||||
@@ -20,9 +16,7 @@ yarn add eslint-plugin-react-hooks --dev
|
||||
|
||||
### Flat Config (eslint.config.js|ts)
|
||||
|
||||
#### >= 6.0.0
|
||||
|
||||
For users of 6.0 and beyond, add the `recommended` config.
|
||||
Add the `recommended` config for all recommended rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
@@ -30,71 +24,42 @@ import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended'],
|
||||
},
|
||||
reactHooks.configs.flat.recommended,
|
||||
]);
|
||||
```
|
||||
|
||||
#### 5.2.0
|
||||
|
||||
For users of 5.2.0 (the first version with flat config support), add the `recommended-latest` config.
|
||||
If you want to try bleeding edge experimental compiler rules, use `recommended-latest`.
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended-latest'],
|
||||
},
|
||||
reactHooks.configs.flat['recommended-latest'],
|
||||
]);
|
||||
```
|
||||
|
||||
### Legacy Config (.eslintrc)
|
||||
|
||||
#### >= 5.2.0
|
||||
|
||||
If you are still using ESLint below 9.0.0, you can use `recommended-legacy` for accessing a legacy version of the recommended config.
|
||||
If you are still using ESLint below 9.0.0, the `recommended` preset can also be used to enable all recommended rules.
|
||||
|
||||
```js
|
||||
{
|
||||
"extends": [
|
||||
// ...
|
||||
"plugin:react-hooks/recommended-legacy"
|
||||
]
|
||||
"extends": ["plugin:react-hooks/recommended"],
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### < 5.2.0
|
||||
|
||||
If you're using a version earlier than 5.2.0, the legacy config was simply `recommended`.
|
||||
|
||||
```js
|
||||
{
|
||||
"extends": [
|
||||
// ...
|
||||
"plugin:react-hooks/recommended"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file:
|
||||
If you want more fine-grained configuration, you can instead choose to enable specific rules. However, we strongly encourage using the recommended presets — see above — so that you will automatically receive new recommended rules as we add them in future versions of the plugin.
|
||||
|
||||
#### Flat Config (eslint.config.js|ts)
|
||||
|
||||
```js
|
||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -102,8 +67,26 @@ export default [
|
||||
plugins: { 'react-hooks': reactHooks },
|
||||
// ...
|
||||
rules: {
|
||||
// Core hooks rules
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
|
||||
// React Compiler rules
|
||||
'react-hooks/config': 'error',
|
||||
'react-hooks/error-boundaries': 'error',
|
||||
'react-hooks/component-hook-factories': 'error',
|
||||
'react-hooks/gating': 'error',
|
||||
'react-hooks/globals': 'error',
|
||||
'react-hooks/immutability': 'error',
|
||||
'react-hooks/preserve-manual-memoization': 'error',
|
||||
'react-hooks/purity': 'error',
|
||||
'react-hooks/refs': 'error',
|
||||
'react-hooks/set-state-in-effect': 'error',
|
||||
'react-hooks/set-state-in-render': 'error',
|
||||
'react-hooks/static-components': 'error',
|
||||
'react-hooks/unsupported-syntax': 'warn',
|
||||
'react-hooks/use-memo': 'error',
|
||||
'react-hooks/incompatible-library': 'warn',
|
||||
}
|
||||
},
|
||||
];
|
||||
@@ -118,8 +101,26 @@ export default [
|
||||
],
|
||||
"rules": {
|
||||
// ...
|
||||
// Core hooks rules
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn"
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
|
||||
// React Compiler rules
|
||||
"react-hooks/config": "error",
|
||||
"react-hooks/error-boundaries": "error",
|
||||
"react-hooks/component-hook-factories": "error",
|
||||
"react-hooks/gating": "error",
|
||||
"react-hooks/globals": "error",
|
||||
"react-hooks/immutability": "error",
|
||||
"react-hooks/preserve-manual-memoization": "error",
|
||||
"react-hooks/purity": "error",
|
||||
"react-hooks/refs": "error",
|
||||
"react-hooks/set-state-in-effect": "error",
|
||||
"react-hooks/set-state-in-render": "error",
|
||||
"react-hooks/static-components": "error",
|
||||
"react-hooks/unsupported-syntax": "warn",
|
||||
"react-hooks/use-memo": "error",
|
||||
"react-hooks/incompatible-library": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './cjs/eslint-plugin-react-hooks';
|
||||
export {default} from './cjs/eslint-plugin-react-hooks';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "5.2.0",
|
||||
"version": "7.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
@@ -41,24 +41,23 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/parser": "^7.24.4",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"hermes-parser": "^0.25.1",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^3.0.3"
|
||||
"zod": "^3.22.4 || ^4.0.0",
|
||||
"zod-validation-error": "^3.0.3 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.11.4",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@babel/types": "^7.19.0",
|
||||
"@tsconfig/strictest": "^2.0.5",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/estree-jsx": "^1.0.5",
|
||||
"@types/node": "^20.2.5",
|
||||
"@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0",
|
||||
"@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0",
|
||||
"@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0",
|
||||
"@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.62.0",
|
||||
"@types/eslint": "^8.56.12",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/estree-jsx": "^1.0.5",
|
||||
"@types/node": "^20.2.5",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint-v7": "npm:eslint@^7.7.0",
|
||||
"eslint-v8": "npm:eslint@^8.57.1",
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
import type {Linter, Rule} from 'eslint';
|
||||
|
||||
import ExhaustiveDeps from './rules/ExhaustiveDeps';
|
||||
import {allRules} from './shared/ReactCompiler';
|
||||
import {
|
||||
allRules,
|
||||
mapErrorSeverityToESlint,
|
||||
recommendedRules,
|
||||
recommendedLatestRules,
|
||||
} from './shared/ReactCompiler';
|
||||
import RulesOfHooks from './rules/RulesOfHooks';
|
||||
|
||||
// All rules
|
||||
const rules = {
|
||||
'exhaustive-deps': ExhaustiveDeps,
|
||||
'rules-of-hooks': RulesOfHooks,
|
||||
@@ -19,47 +23,74 @@ const rules = {
|
||||
),
|
||||
} satisfies Record<string, Rule.RuleModule>;
|
||||
|
||||
// Config rules
|
||||
const ruleConfigs = {
|
||||
const basicRuleConfigs = {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
} satisfies Linter.RulesRecord;
|
||||
} as const satisfies Linter.RulesRecord;
|
||||
|
||||
const recommendedCompilerRuleConfigs = 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>;
|
||||
|
||||
const recommendedLatestCompilerRuleConfigs = Object.fromEntries(
|
||||
Object.entries(recommendedLatestRules).map(([name, ruleConfig]) => {
|
||||
return [
|
||||
`react-hooks/${name}` as const,
|
||||
mapErrorSeverityToESlint(ruleConfig.severity),
|
||||
] as const;
|
||||
}),
|
||||
) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
|
||||
|
||||
const recommendedRuleConfigs: Linter.RulesRecord = {
|
||||
...basicRuleConfigs,
|
||||
...recommendedCompilerRuleConfigs,
|
||||
};
|
||||
const recommendedLatestRuleConfigs: Linter.RulesRecord = {
|
||||
...basicRuleConfigs,
|
||||
...recommendedLatestCompilerRuleConfigs,
|
||||
};
|
||||
|
||||
const plugins = ['react-hooks'];
|
||||
|
||||
type ReactHooksFlatConfig = {
|
||||
plugins: {react: any};
|
||||
rules: Linter.RulesRecord;
|
||||
};
|
||||
|
||||
const configs = {
|
||||
recommended: {
|
||||
plugins,
|
||||
rules: recommendedRuleConfigs,
|
||||
},
|
||||
'recommended-latest': {
|
||||
plugins,
|
||||
rules: recommendedLatestRuleConfigs,
|
||||
},
|
||||
flat: {} as Record<string, ReactHooksFlatConfig>,
|
||||
};
|
||||
|
||||
const plugin = {
|
||||
meta: {
|
||||
name: 'eslint-plugin-react-hooks',
|
||||
version: '7.0.0',
|
||||
},
|
||||
configs: {},
|
||||
rules,
|
||||
configs,
|
||||
};
|
||||
|
||||
Object.assign(plugin.configs, {
|
||||
'recommended-legacy': {
|
||||
plugins: ['react-hooks'],
|
||||
rules: ruleConfigs,
|
||||
Object.assign(configs.flat, {
|
||||
'recommended-latest': {
|
||||
plugins: {'react-hooks': plugin},
|
||||
rules: configs['recommended-latest'].rules,
|
||||
},
|
||||
|
||||
'flat/recommended': [
|
||||
{
|
||||
plugins: {
|
||||
'react-hooks': plugin,
|
||||
},
|
||||
rules: ruleConfigs,
|
||||
},
|
||||
],
|
||||
|
||||
'recommended-latest': [
|
||||
{
|
||||
plugins: {
|
||||
'react-hooks': plugin,
|
||||
},
|
||||
rules: ruleConfigs,
|
||||
},
|
||||
],
|
||||
|
||||
recommended: {
|
||||
plugins: ['react-hooks'],
|
||||
rules: ruleConfigs,
|
||||
plugins: {'react-hooks': plugin},
|
||||
rules: configs.recommended.rules,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
LintRules,
|
||||
type LintRule,
|
||||
ErrorSeverity,
|
||||
LintRulePreset,
|
||||
} from 'babel-plugin-react-compiler';
|
||||
import {type Linter, type Rule} from 'eslint';
|
||||
import runReactCompiler, {RunCacheEntry} from './RunReactCompiler';
|
||||
@@ -149,7 +150,7 @@ function makeRule(rule: LintRule): Rule.RuleModule {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: rule.description,
|
||||
recommended: rule.recommended,
|
||||
recommended: rule.preset === LintRulePreset.Recommended,
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
@@ -160,69 +161,30 @@ 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,
|
||||
);
|
||||
rule => rule.preset === LintRulePreset.Recommended,
|
||||
).reduce((acc, rule) => {
|
||||
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
|
||||
return acc;
|
||||
}, {} as RulesConfig);
|
||||
|
||||
export const recommendedLatestRules: RulesConfig = LintRules.filter(
|
||||
rule =>
|
||||
rule.preset === LintRulePreset.Recommended ||
|
||||
rule.preset === LintRulePreset.RecommendedLatest,
|
||||
).reduce((acc, rule) => {
|
||||
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
|
||||
return acc;
|
||||
}, {} as RulesConfig);
|
||||
|
||||
export function mapErrorSeverityToESlint(
|
||||
severity: ErrorSeverity,
|
||||
|
||||
@@ -6,21 +6,17 @@
|
||||
*/
|
||||
/* 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';
|
||||
// @ts-expect-error: no types available
|
||||
import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
|
||||
import {File} from '@babel/types';
|
||||
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 +42,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<UnusedOptOutDirective>;
|
||||
events: Array<LoggerEvent>;
|
||||
};
|
||||
|
||||
@@ -88,25 +78,6 @@ function getFlowSuppressions(
|
||||
return results;
|
||||
}
|
||||
|
||||
function filterUnusedOptOutDirectives(
|
||||
directives: ReadonlyArray<Directive>,
|
||||
): Array<UnusedOptOutDirective> {
|
||||
const results: Array<UnusedOptOutDirective> = [];
|
||||
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,
|
||||
@@ -126,7 +97,6 @@ function runReactCompilerImpl({
|
||||
filename,
|
||||
userOpts,
|
||||
flowSuppressions: [],
|
||||
unusedOptOutDirectives: [],
|
||||
events: [],
|
||||
};
|
||||
const userLogger: Logger | null = options.logger;
|
||||
@@ -144,6 +114,7 @@ function runReactCompilerImpl({
|
||||
}
|
||||
|
||||
let babelAST: ParseResult<File> | null = null;
|
||||
|
||||
if (filename.endsWith('.tsx') || filename.endsWith('.ts')) {
|
||||
try {
|
||||
babelAST = babelParse(sourceCode.text, {
|
||||
@@ -174,36 +145,11 @@ function runReactCompilerImpl({
|
||||
filename,
|
||||
highlightCode: false,
|
||||
retainLines: true,
|
||||
plugins: [
|
||||
[PluginProposalPrivateMethods, {loose: true}],
|
||||
[BabelPluginReactCompiler, options],
|
||||
],
|
||||
plugins: [[BabelPluginReactCompiler, options]],
|
||||
sourceType: 'module',
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
});
|
||||
|
||||
if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
|
||||
traverse(babelAST, {
|
||||
FunctionDeclaration(path) {
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
},
|
||||
ArrowFunctionExpression(path) {
|
||||
if (path.node.body.type === 'BlockStatement') {
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
}
|
||||
},
|
||||
FunctionExpression(path) {
|
||||
results.unusedOptOutDirectives.push(
|
||||
...filterUnusedOptOutDirectives(path.node.body.directives),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
/* errors handled by injected logger */
|
||||
}
|
||||
|
||||
99
packages/react-client/src/ReactFlightClient.js
vendored
99
packages/react-client/src/ReactFlightClient.js
vendored
@@ -367,6 +367,7 @@ type Response = {
|
||||
_debugRootStack?: null | Error, // DEV-only
|
||||
_debugRootTask?: null | ConsoleTask, // DEV-only
|
||||
_debugStartTime: number, // DEV-only
|
||||
_debugIOStarted: boolean, // DEV-only
|
||||
_debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
|
||||
_debugChannel?: void | DebugChannel, // DEV-only
|
||||
_blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only
|
||||
@@ -500,7 +501,7 @@ function createErrorChunk<T>(
|
||||
}
|
||||
|
||||
function moveDebugInfoFromChunkToInnerValue<T>(
|
||||
chunk: InitializedChunk<T>,
|
||||
chunk: InitializedChunk<T> | InitializedStreamChunk<any>,
|
||||
value: T,
|
||||
): void {
|
||||
// Remove the debug info from the initialized chunk, and add it to the inner
|
||||
@@ -1569,6 +1570,10 @@ function fulfillReference(
|
||||
initializedChunk.reason = handler.reason; // Used by streaming chunks
|
||||
if (resolveListeners !== null) {
|
||||
wakeChunk(resolveListeners, handler.value, initializedChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
moveDebugInfoFromChunkToInnerValue(initializedChunk, handler.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1818,6 +1823,10 @@ function loadServerReference<A: Iterable<any>, T>(
|
||||
initializedChunk.value = handler.value;
|
||||
if (resolveListeners !== null) {
|
||||
wakeChunk(resolveListeners, handler.value, initializedChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
moveDebugInfoFromChunkToInnerValue(initializedChunk, handler.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2536,6 +2545,10 @@ function missingCall() {
|
||||
);
|
||||
}
|
||||
|
||||
function markIOStarted(this: Response) {
|
||||
this._debugIOStarted = true;
|
||||
}
|
||||
|
||||
function ResponseInstance(
|
||||
this: $FlowFixMe,
|
||||
bundlerConfig: ServerConsumerModuleMap,
|
||||
@@ -2609,6 +2622,10 @@ function ResponseInstance(
|
||||
// where as if you use createFromReadableStream from the body of the fetch
|
||||
// then the start time is when the headers resolved.
|
||||
this._debugStartTime = performance.now();
|
||||
this._debugIOStarted = false;
|
||||
// We consider everything before the first setTimeout task to be cached data
|
||||
// and is not considered I/O required to load the stream.
|
||||
setTimeout(markIOStarted.bind(this), 0);
|
||||
}
|
||||
this._debugFindSourceMapURL = findSourceMapURL;
|
||||
this._debugChannel = debugChannel;
|
||||
@@ -2762,7 +2779,7 @@ function incrementChunkDebugInfo(
|
||||
}
|
||||
}
|
||||
|
||||
function addDebugInfo(chunk: SomeChunk<any>, debugInfo: ReactDebugInfo): void {
|
||||
function addAsyncInfo(chunk: SomeChunk<any>, asyncInfo: ReactAsyncInfo): void {
|
||||
const value = resolveLazy(chunk.value);
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
@@ -2774,34 +2791,39 @@ function addDebugInfo(chunk: SomeChunk<any>, debugInfo: ReactDebugInfo): void {
|
||||
) {
|
||||
if (isArray(value._debugInfo)) {
|
||||
// $FlowFixMe[method-unbinding]
|
||||
value._debugInfo.push.apply(value._debugInfo, debugInfo);
|
||||
value._debugInfo.push(asyncInfo);
|
||||
} else {
|
||||
Object.defineProperty((value: any), '_debugInfo', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: debugInfo,
|
||||
value: [asyncInfo],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// $FlowFixMe[method-unbinding]
|
||||
chunk._debugInfo.push.apply(chunk._debugInfo, debugInfo);
|
||||
chunk._debugInfo.push(asyncInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveChunkDebugInfo(
|
||||
response: Response,
|
||||
streamState: StreamState,
|
||||
chunk: SomeChunk<any>,
|
||||
): void {
|
||||
if (__DEV__ && enableAsyncDebugInfo) {
|
||||
// Add the currently resolving chunk's debug info representing the stream
|
||||
// to the Promise that was waiting on the stream, or its underlying value.
|
||||
const debugInfo: ReactDebugInfo = [{awaited: streamState._debugInfo}];
|
||||
if (chunk.status === PENDING || chunk.status === BLOCKED) {
|
||||
const boundAddDebugInfo = addDebugInfo.bind(null, chunk, debugInfo);
|
||||
chunk.then(boundAddDebugInfo, boundAddDebugInfo);
|
||||
} else {
|
||||
addDebugInfo(chunk, debugInfo);
|
||||
// Only include stream information after a macrotask. Any chunk processed
|
||||
// before that is considered cached data.
|
||||
if (response._debugIOStarted) {
|
||||
// Add the currently resolving chunk's debug info representing the stream
|
||||
// to the Promise that was waiting on the stream, or its underlying value.
|
||||
const asyncInfo: ReactAsyncInfo = {awaited: streamState._debugInfo};
|
||||
if (chunk.status === PENDING || chunk.status === BLOCKED) {
|
||||
const boundAddAsyncInfo = addAsyncInfo.bind(null, chunk, asyncInfo);
|
||||
chunk.then(boundAddAsyncInfo, boundAddAsyncInfo);
|
||||
} else {
|
||||
addAsyncInfo(chunk, asyncInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2837,12 +2859,12 @@ function resolveModel(
|
||||
model,
|
||||
);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, chunk);
|
||||
resolveChunkDebugInfo(response, streamState, chunk);
|
||||
}
|
||||
resolveModelChunk(response, chunk, model);
|
||||
}
|
||||
@@ -2869,7 +2891,7 @@ function resolveText(
|
||||
}
|
||||
const newChunk = createInitializedTextChunk(response, text);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
}
|
||||
@@ -2895,7 +2917,7 @@ function resolveBuffer(
|
||||
}
|
||||
const newChunk = createInitializedBufferChunk(response, buffer);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
}
|
||||
@@ -2942,7 +2964,7 @@ function resolveModule(
|
||||
blockedChunk.status = BLOCKED;
|
||||
}
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, blockedChunk);
|
||||
resolveChunkDebugInfo(response, streamState, blockedChunk);
|
||||
}
|
||||
promise.then(
|
||||
() => resolveModuleChunk(response, blockedChunk, clientReference),
|
||||
@@ -2952,12 +2974,12 @@ function resolveModule(
|
||||
if (!chunk) {
|
||||
const newChunk = createResolvedModuleChunk(response, clientReference);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, chunk);
|
||||
resolveChunkDebugInfo(response, streamState, chunk);
|
||||
}
|
||||
// This can't actually happen because we don't have any forward
|
||||
// references to modules.
|
||||
@@ -2978,13 +3000,13 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
|
||||
if (!chunk) {
|
||||
const newChunk = createInitializedStreamChunk(response, stream, controller);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
return;
|
||||
}
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, chunk);
|
||||
resolveChunkDebugInfo(response, streamState, chunk);
|
||||
}
|
||||
if (chunk.status !== PENDING) {
|
||||
// We already resolved. We didn't expect to see this.
|
||||
@@ -3034,6 +3056,10 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
|
||||
resolvedChunk.reason = controller;
|
||||
if (resolveListeners !== null) {
|
||||
wakeChunk(resolveListeners, chunk.value, (chunk: any));
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
moveDebugInfoFromChunkToInnerValue(resolvedChunk, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3433,12 +3459,12 @@ function resolvePostponeDev(
|
||||
postponeInstance,
|
||||
);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, chunk);
|
||||
resolveChunkDebugInfo(response, streamState, chunk);
|
||||
}
|
||||
triggerErrorOnChunk(response, chunk, postponeInstance);
|
||||
}
|
||||
@@ -3467,12 +3493,12 @@ function resolveErrorModel(
|
||||
errorWithDigest,
|
||||
);
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, newChunk);
|
||||
resolveChunkDebugInfo(response, streamState, newChunk);
|
||||
}
|
||||
chunks.set(id, newChunk);
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
resolveChunkDebugInfo(streamState, chunk);
|
||||
resolveChunkDebugInfo(response, streamState, chunk);
|
||||
}
|
||||
triggerErrorOnChunk(response, chunk, errorWithDigest);
|
||||
}
|
||||
@@ -4318,7 +4344,26 @@ function flushComponentPerformance(
|
||||
|
||||
// First find the start time of the first component to know if it was running
|
||||
// in parallel with the previous.
|
||||
const debugInfo = __DEV__ && root._debugInfo;
|
||||
let debugInfo = null;
|
||||
if (__DEV__) {
|
||||
debugInfo = root._debugInfo;
|
||||
if (debugInfo.length === 0 && root.status === 'fulfilled') {
|
||||
const resolvedValue = resolveLazy(root.value);
|
||||
if (
|
||||
typeof resolvedValue === 'object' &&
|
||||
resolvedValue !== null &&
|
||||
(isArray(resolvedValue) ||
|
||||
typeof resolvedValue[ASYNC_ITERATOR] === 'function' ||
|
||||
resolvedValue.$$typeof === REACT_ELEMENT_TYPE ||
|
||||
resolvedValue.$$typeof === REACT_LAZY_TYPE) &&
|
||||
isArray(resolvedValue._debugInfo)
|
||||
) {
|
||||
// It's possible that the value has been given the debug info.
|
||||
// In that case we need to look for it on the resolved value.
|
||||
debugInfo = resolvedValue._debugInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debugInfo) {
|
||||
let startTime = 0;
|
||||
for (let i = 0; i < debugInfo.length; i++) {
|
||||
|
||||
@@ -102,6 +102,7 @@ export function logComponentRender(
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
const debugTask = componentInfo.debugTask;
|
||||
const measureName = '\u200b' + entryName;
|
||||
if (__DEV__ && debugTask) {
|
||||
const properties: Array<[string, string]> = [];
|
||||
if (componentInfo.key != null) {
|
||||
@@ -110,9 +111,10 @@ export function logComponentRender(
|
||||
if (componentInfo.props != null) {
|
||||
addObjectToProperties(componentInfo.props, properties, 0, '');
|
||||
}
|
||||
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
performance.measure.bind(performance, '\u200b' + entryName, {
|
||||
performance.measure.bind(performance, measureName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
@@ -125,9 +127,10 @@ export function logComponentRender(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(measureName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
'\u200b' + entryName,
|
||||
measureName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
@@ -152,6 +155,7 @@ export function logComponentAborted(
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
const measureName = '\u200b' + entryName;
|
||||
if (__DEV__) {
|
||||
const properties: Array<[string, string]> = [
|
||||
[
|
||||
@@ -165,7 +169,8 @@ export function logComponentAborted(
|
||||
if (componentInfo.props != null) {
|
||||
addObjectToProperties(componentInfo.props, properties, 0, '');
|
||||
}
|
||||
performance.measure('\u200b' + entryName, {
|
||||
|
||||
performance.measure(measureName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
@@ -178,9 +183,10 @@ export function logComponentAborted(
|
||||
},
|
||||
},
|
||||
});
|
||||
performance.clearMeasures(measureName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
measureName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
@@ -206,6 +212,7 @@ export function logComponentErrored(
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
const measureName = '\u200b' + entryName;
|
||||
if (__DEV__) {
|
||||
const message =
|
||||
typeof error === 'object' &&
|
||||
@@ -222,7 +229,8 @@ export function logComponentErrored(
|
||||
if (componentInfo.props != null) {
|
||||
addObjectToProperties(componentInfo.props, properties, 0, '');
|
||||
}
|
||||
performance.measure('\u200b' + entryName, {
|
||||
|
||||
performance.measure(measureName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
@@ -235,9 +243,10 @@ export function logComponentErrored(
|
||||
},
|
||||
},
|
||||
});
|
||||
performance.clearMeasures(measureName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
measureName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
@@ -397,6 +406,7 @@ export function logComponentAwaitAborted(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(entryName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
@@ -453,6 +463,7 @@ export function logComponentAwaitErrored(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(entryName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
@@ -514,6 +525,7 @@ export function logComponentAwait(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(entryName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
@@ -538,6 +550,7 @@ export function logIOInfoErrored(
|
||||
const description = getIODescription(error);
|
||||
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
|
||||
const debugTask = ioInfo.debugTask;
|
||||
const measureName = '\u200b' + entryName;
|
||||
if (__DEV__ && debugTask) {
|
||||
const message =
|
||||
typeof error === 'object' &&
|
||||
@@ -550,9 +563,10 @@ export function logIOInfoErrored(
|
||||
const properties = [['rejected with', message]];
|
||||
const tooltipText =
|
||||
getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
|
||||
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
performance.measure.bind(performance, '\u200b' + entryName, {
|
||||
performance.measure.bind(performance, measureName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: endTime,
|
||||
detail: {
|
||||
@@ -565,9 +579,10 @@ export function logIOInfoErrored(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(measureName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
measureName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
IO_TRACK,
|
||||
@@ -590,6 +605,7 @@ export function logIOInfo(
|
||||
const entryName = getIOShortName(ioInfo, description, ioInfo.env, rootEnv);
|
||||
const color = getIOColor(entryName);
|
||||
const debugTask = ioInfo.debugTask;
|
||||
const measureName = '\u200b' + entryName;
|
||||
if (__DEV__ && debugTask) {
|
||||
const properties: Array<[string, string]> = [];
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
@@ -605,7 +621,7 @@ export function logIOInfo(
|
||||
);
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
performance.measure.bind(performance, '\u200b' + entryName, {
|
||||
performance.measure.bind(performance, measureName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: endTime,
|
||||
detail: {
|
||||
@@ -618,9 +634,10 @@ export function logIOInfo(
|
||||
},
|
||||
}),
|
||||
);
|
||||
performance.clearMeasures(measureName);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
measureName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
IO_TRACK,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-devtools-core",
|
||||
"version": "6.1.5",
|
||||
"version": "7.0.0",
|
||||
"description": "Use react-devtools outside of the browser",
|
||||
"license": "MIT",
|
||||
"main": "./dist/backend.js",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"manifest_version": 3,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Chrome Developer Tools.",
|
||||
"version": "6.1.5",
|
||||
"version_name": "6.1.5",
|
||||
"version": "7.0.0",
|
||||
"version_name": "7.0.0",
|
||||
"minimum_chrome_version": "114",
|
||||
"icons": {
|
||||
"16": "icons/16-production.png",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"manifest_version": 3,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
|
||||
"version": "6.1.5",
|
||||
"version_name": "6.1.5",
|
||||
"version": "7.0.0",
|
||||
"version_name": "7.0.0",
|
||||
"minimum_chrome_version": "114",
|
||||
"icons": {
|
||||
"16": "icons/16-production.png",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Firefox Developer Tools.",
|
||||
"version": "6.1.5",
|
||||
"version": "7.0.0",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "@react-devtools",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-devtools-inline",
|
||||
"version": "6.1.5",
|
||||
"version": "7.0.0",
|
||||
"description": "Embed react-devtools within a website",
|
||||
"license": "MIT",
|
||||
"main": "./dist/backend.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() {
|
||||
|
||||
@@ -974,12 +974,8 @@ describe('Store', () => {
|
||||
<Suspense name="three" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
`);
|
||||
|
||||
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: [],
|
||||
});
|
||||
});
|
||||
@@ -3148,4 +3142,105 @@ describe('Store', () => {
|
||||
await actAsync(() => render(null));
|
||||
expect(store).toMatchInlineSnapshot(``);
|
||||
});
|
||||
|
||||
// @reactVersion >= 19
|
||||
it('should keep suspended boundaries in the Suspense tree but not hidden Activity', async () => {
|
||||
const Activity = React.Activity || React.unstable_Activity;
|
||||
|
||||
const never = new Promise(() => {});
|
||||
function Never() {
|
||||
readValue(never);
|
||||
return null;
|
||||
}
|
||||
function Component({children}) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
function App({hidden}) {
|
||||
return (
|
||||
<>
|
||||
<Activity mode={hidden ? 'hidden' : 'visible'}>
|
||||
<React.Suspense name="inside-activity">
|
||||
<Component key="inside-activity">inside Activity</Component>
|
||||
</React.Suspense>
|
||||
</Activity>
|
||||
<React.Suspense name="outer-suspense">
|
||||
<React.Suspense name="inner-suspense">
|
||||
<Component key="inside-suspense">inside Suspense</Component>
|
||||
</React.Suspense>
|
||||
{hidden ? <Never /> : null}
|
||||
</React.Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
await actAsync(() => {
|
||||
render(<App hidden={true} />);
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <App>
|
||||
<Activity>
|
||||
<Suspense name="outer-suspense">
|
||||
[suspense-root] rects={[{x:1,y:2,width:15,height:1}]}
|
||||
<Suspense name="outer-suspense" rects={null}>
|
||||
`);
|
||||
|
||||
// mount as visible
|
||||
await actAsync(() => {
|
||||
render(null);
|
||||
});
|
||||
await actAsync(() => {
|
||||
render(<App hidden={false} />);
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <App>
|
||||
▾ <Activity>
|
||||
▾ <Suspense name="inside-activity">
|
||||
<Component key="inside-activity">
|
||||
▾ <Suspense name="outer-suspense">
|
||||
▾ <Suspense name="inner-suspense">
|
||||
<Component key="inside-suspense">
|
||||
[suspense-root] rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:15,height:1}]}
|
||||
<Suspense name="inside-activity" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
<Suspense name="outer-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
<Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
`);
|
||||
|
||||
await actAsync(() => {
|
||||
render(<App hidden={true} />);
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <App>
|
||||
<Activity>
|
||||
<Suspense name="outer-suspense">
|
||||
[suspense-root] rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:15,height:1}]}
|
||||
<Suspense name="outer-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
<Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
`);
|
||||
|
||||
await actAsync(() => {
|
||||
render(<App hidden={false} />);
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <App>
|
||||
▾ <Activity>
|
||||
▾ <Suspense name="inside-activity">
|
||||
<Component key="inside-activity">
|
||||
▾ <Suspense name="outer-suspense">
|
||||
▾ <Suspense name="inner-suspense">
|
||||
<Component key="inside-suspense">
|
||||
[suspense-root] rects={[{x:1,y:2,width:15,height:1}, {x:1,y:2,width:15,height:1}]}
|
||||
<Suspense name="inside-activity" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
<Suspense name="outer-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
<Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -251,7 +251,8 @@ describe('Store component filters', () => {
|
||||
});
|
||||
|
||||
it('should filter ViewTransition', async () => {
|
||||
const ViewTransition = React.unstable_ViewTransition;
|
||||
const ViewTransition =
|
||||
React.ViewTransition || React.unstable_ViewTransition;
|
||||
|
||||
if (ViewTransition != null) {
|
||||
await actAsync(async () =>
|
||||
|
||||
278
packages/react-devtools-shared/src/backend/agent.js
vendored
278
packages/react-devtools-shared/src/backend/agent.js
vendored
@@ -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<string | number> | null,
|
||||
requestID: number,
|
||||
};
|
||||
|
||||
type OverrideHookParams = {
|
||||
id: number,
|
||||
hookID: number,
|
||||
@@ -131,8 +147,6 @@ type OverrideSuspenseParams = {
|
||||
};
|
||||
|
||||
type OverrideSuspenseMilestoneParams = {
|
||||
rendererID: number,
|
||||
rootID: number,
|
||||
suspendedSet: Array<number>,
|
||||
};
|
||||
|
||||
@@ -141,6 +155,111 @@ type PersistedSelection = {
|
||||
path: Array<PathFrame>,
|
||||
};
|
||||
|
||||
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<mixed>);
|
||||
const rightSuspendedByData = ((data: any): Array<mixed>);
|
||||
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<mixed>);
|
||||
suspendedByOffset += suspendedBy.length;
|
||||
found = true;
|
||||
break;
|
||||
case 'no-change':
|
||||
found = true;
|
||||
const rootsSuspendedBy: Array<mixed> =
|
||||
(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');
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -3145,12 +3144,30 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offscreen of suspended Suspense
|
||||
*/
|
||||
function isSuspendedOffscreen(fiber: Fiber): boolean {
|
||||
switch (fiber.tag) {
|
||||
case LegacyHiddenComponent:
|
||||
// fallthrough since all published implementations currently implement the same state as Offscreen.
|
||||
case OffscreenComponent:
|
||||
return (
|
||||
fiber.memoizedState !== null &&
|
||||
fiber.return !== null &&
|
||||
fiber.return.tag === SuspenseComponent
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function unmountRemainingChildren() {
|
||||
if (
|
||||
reconcilingParent !== null &&
|
||||
(reconcilingParent.kind === FIBER_INSTANCE ||
|
||||
reconcilingParent.kind === FILTERED_FIBER_INSTANCE) &&
|
||||
isHiddenOffscreen(reconcilingParent.data) &&
|
||||
isSuspendedOffscreen(reconcilingParent.data) &&
|
||||
!isInDisconnectedSubtree
|
||||
) {
|
||||
// This is a hidden offscreen, we need to execute this in the context of a disconnected subtree.
|
||||
@@ -4027,7 +4044,7 @@ export function attach(
|
||||
trackDebugInfoFromHostComponent(nearestInstance, fiber);
|
||||
}
|
||||
|
||||
if (isHiddenOffscreen(fiber)) {
|
||||
if (isSuspendedOffscreen(fiber)) {
|
||||
// If an Offscreen component is hidden, mount its children as disconnected.
|
||||
const stashedDisconnected = isInDisconnectedSubtree;
|
||||
isInDisconnectedSubtree = true;
|
||||
@@ -4038,6 +4055,9 @@ export function attach(
|
||||
} finally {
|
||||
isInDisconnectedSubtree = stashedDisconnected;
|
||||
}
|
||||
} else if (isHiddenOffscreen(fiber)) {
|
||||
// hidden Activity is noisy.
|
||||
// Including it may show overlapping Suspense rects
|
||||
} else if (fiber.tag === SuspenseComponent && OffscreenComponent === -1) {
|
||||
// Legacy Suspense without the Offscreen wrapper. For the modern Suspense we just handle the
|
||||
// Offscreen wrapper itself specially.
|
||||
@@ -4902,7 +4922,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.
|
||||
@@ -4978,6 +5002,8 @@ export function attach(
|
||||
|
||||
const prevWasHidden = isHiddenOffscreen(prevFiber);
|
||||
const nextIsHidden = isHiddenOffscreen(nextFiber);
|
||||
const prevWasSuspended = isSuspendedOffscreen(prevFiber);
|
||||
const nextIsSuspended = isSuspendedOffscreen(nextFiber);
|
||||
|
||||
if (isLegacySuspense) {
|
||||
if (
|
||||
@@ -5055,8 +5081,8 @@ export function attach(
|
||||
);
|
||||
updateFlags |= ShouldResetChildren | ShouldResetSuspenseChildren;
|
||||
}
|
||||
} else if (nextIsHidden) {
|
||||
if (!prevWasHidden) {
|
||||
} else if (nextIsSuspended) {
|
||||
if (!prevWasSuspended) {
|
||||
// We're hiding the children. Disconnect them from the front end but keep state.
|
||||
if (fiberInstance !== null && !isInDisconnectedSubtree) {
|
||||
disconnectChildrenRecursively(remainingReconcilingChildren);
|
||||
@@ -5074,7 +5100,7 @@ export function attach(
|
||||
} finally {
|
||||
isInDisconnectedSubtree = stashedDisconnected;
|
||||
}
|
||||
} else if (prevWasHidden && !nextIsHidden) {
|
||||
} else if (prevWasSuspended && !nextIsSuspended) {
|
||||
// We're revealing the hidden children. We now need to update them to the latest state.
|
||||
// We do this while still in the disconnected state and then we reconnect the new ones.
|
||||
// This avoids reconnecting things that are about to be removed anyway.
|
||||
@@ -5100,6 +5126,13 @@ export function attach(
|
||||
// Children may have reordered while they were hidden.
|
||||
updateFlags |= ShouldResetChildren | ShouldResetSuspenseChildren;
|
||||
}
|
||||
} else if (nextIsHidden) {
|
||||
if (prevWasHidden) {
|
||||
// still hidden. Nothing to do.
|
||||
} else {
|
||||
// We're hiding the children. Remove them from the Frontend
|
||||
unmountRemainingChildren();
|
||||
}
|
||||
} else if (
|
||||
nextFiber.tag === SuspenseComponent &&
|
||||
OffscreenComponent !== -1 &&
|
||||
@@ -5256,7 +5289,7 @@ export function attach(
|
||||
// We need to crawl the subtree for closest non-filtered Fibers
|
||||
// so that we can display them in a flat children set.
|
||||
if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
|
||||
if (!nextIsHidden && !isInDisconnectedSubtree) {
|
||||
if (!nextIsSuspended && !isInDisconnectedSubtree) {
|
||||
recordResetChildren(fiberInstance);
|
||||
}
|
||||
|
||||
@@ -5332,7 +5365,7 @@ export function attach(
|
||||
if (
|
||||
(child.kind === FIBER_INSTANCE ||
|
||||
child.kind === FILTERED_FIBER_INSTANCE) &&
|
||||
isHiddenOffscreen(child.data)
|
||||
isSuspendedOffscreen(child.data)
|
||||
) {
|
||||
// This instance's children are already disconnected.
|
||||
} else {
|
||||
@@ -6422,7 +6455,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 +6626,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 +6697,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 +6735,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 +6908,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 +7107,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 +7256,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 +8007,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<FiberInstance['id']>,
|
||||
) {
|
||||
function overrideSuspenseMilestone(suspendedSet: Array<FiberInstance['id']>) {
|
||||
if (
|
||||
typeof setSuspenseHandler !== 'function' ||
|
||||
typeof scheduleUpdate !== 'function'
|
||||
@@ -7883,8 +8019,6 @@ export function attach(
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Allow overriding the timeline for the specified root.
|
||||
|
||||
const unsuspendedSet: Set<Fiber> = new Set(forceFallbackForFibers);
|
||||
|
||||
let resuspended = false;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<number>,
|
||||
) => void,
|
||||
overrideSuspenseMilestone: (suspendedSet: Array<number>) => void,
|
||||
overrideValueAtPath: (
|
||||
type: Type,
|
||||
id: number,
|
||||
|
||||
@@ -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<HostInstance> = [];
|
||||
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,
|
||||
|
||||
28
packages/react-devtools-shared/src/backendAPI.js
vendored
28
packages/react-devtools-shared/src/backendAPI.js
vendored
@@ -95,7 +95,7 @@ export function inspectElement(
|
||||
id: number,
|
||||
path: InspectedElementPath | null,
|
||||
rendererID: number,
|
||||
shouldListenToPauseEvents: boolean = false,
|
||||
shouldListenToPauseEvents: boolean,
|
||||
): Promise<InspectedElementPayload> {
|
||||
const requestID = requestCounter++;
|
||||
const promise = getPromiseForRequestID<InspectedElementPayload>(
|
||||
@@ -117,6 +117,32 @@ export function inspectElement(
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function inspectScreen(
|
||||
bridge: FrontendBridge,
|
||||
forceFullData: boolean,
|
||||
arbitraryRootID: number,
|
||||
path: InspectedElementPath | null,
|
||||
shouldListenToPauseEvents: boolean,
|
||||
): Promise<InspectedElementPayload> {
|
||||
const requestID = requestCounter++;
|
||||
const promise = getPromiseForRequestID<InspectedElementPayload>(
|
||||
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({
|
||||
|
||||
24
packages/react-devtools-shared/src/bridge.js
vendored
24
packages/react-devtools-shared/src/bridge.js
vendored
@@ -65,12 +65,6 @@ export const BRIDGE_PROTOCOL: Array<BridgeProtocol> = [
|
||||
{
|
||||
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<ElementAndRendererID>,
|
||||
displayName: string | null,
|
||||
hideAfterTimeout: boolean,
|
||||
scrollIntoView: boolean,
|
||||
};
|
||||
|
||||
type ScrollToHostInstance = {
|
||||
...ElementAndRendererID,
|
||||
@@ -145,8 +145,6 @@ type OverrideSuspense = {
|
||||
};
|
||||
|
||||
type OverrideSuspenseMilestone = {
|
||||
rendererID: number,
|
||||
rootID: number,
|
||||
suspendedSet: Array<number>,
|
||||
};
|
||||
|
||||
@@ -167,6 +165,13 @@ type InspectElementParams = {
|
||||
requestID: number,
|
||||
};
|
||||
|
||||
type InspectScreenParams = {
|
||||
requestID: number,
|
||||
id: number,
|
||||
forceFullData: boolean,
|
||||
path: Array<number | string> | null,
|
||||
};
|
||||
|
||||
type StoreAsGlobalParams = {
|
||||
...ElementAndRendererID,
|
||||
count: number,
|
||||
@@ -199,6 +204,7 @@ export type BackendEvents = {
|
||||
fastRefreshScheduled: [],
|
||||
getSavedPreferences: [],
|
||||
inspectedElement: [InspectedElementPayload],
|
||||
inspectedScreen: [InspectedElementPayload],
|
||||
isReloadAndProfileSupportedByBackend: [boolean],
|
||||
operations: [Array<number>],
|
||||
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],
|
||||
|
||||
@@ -51,6 +51,7 @@ import type {
|
||||
ComponentFilter,
|
||||
ElementType,
|
||||
SuspenseNode,
|
||||
Rect,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import type {
|
||||
FrontendBridge,
|
||||
@@ -96,10 +97,13 @@ export type Capabilities = {
|
||||
supportsBasicProfiling: boolean,
|
||||
hasOwnerMetadata: boolean,
|
||||
supportsStrictMode: boolean,
|
||||
supportsTogglingSuspense: boolean,
|
||||
supportsAdvancedProfiling: AdvancedProfiling,
|
||||
};
|
||||
|
||||
function isNonZeroRect(rect: Rect) {
|
||||
return rect.width > 0 || rect.height > 0 || rect.x > 0 || rect.y > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The store is the single source of truth for updates from the backend.
|
||||
* ContextProviders can subscribe to the Store for specific things they want to provide.
|
||||
@@ -506,14 +510,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 +894,56 @@ export default class Store extends EventEmitter<{
|
||||
* @param uniqueSuspendersOnly Filters out boundaries without unique suspenders
|
||||
*/
|
||||
getSuspendableDocumentOrderSuspense(
|
||||
rootID: Element['id'] | void,
|
||||
uniqueSuspendersOnly: boolean,
|
||||
): $ReadOnlyArray<SuspenseNode['id']> {
|
||||
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;
|
||||
}
|
||||
// Ignore any suspense boundaries that has no visual representation as this is not
|
||||
// part of the visible loading sequence.
|
||||
// TODO: Consider making visible meta data and other side-effects get virtual rects.
|
||||
const hasRects =
|
||||
current.rects !== null &&
|
||||
current.rects.length > 0 &&
|
||||
current.rects.some(isNonZeroRect);
|
||||
if (
|
||||
hasRects &&
|
||||
(!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 +1205,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 +1217,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 +1225,6 @@ export default class Store extends EventEmitter<{
|
||||
supportsBasicProfiling,
|
||||
hasOwnerMetadata,
|
||||
supportsStrictMode,
|
||||
supportsTogglingSuspense,
|
||||
supportsAdvancedProfiling,
|
||||
});
|
||||
|
||||
@@ -1561,7 +1570,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,9 +181,7 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
className={styles.KeyValue}
|
||||
title={key}
|
||||
onDoubleClick={handleKeyDoubleClick}>
|
||||
<pre>
|
||||
<IndexableDisplayName displayName={key} id={id} />
|
||||
</pre>
|
||||
<IndexableDisplayName displayName={key} id={id} />
|
||||
</span>
|
||||
"
|
||||
</Fragment>
|
||||
@@ -196,9 +194,7 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
className={styles.KeyValue}
|
||||
title={nameProp}
|
||||
onDoubleClick={handleKeyDoubleClick}>
|
||||
<pre>
|
||||
<IndexableDisplayName displayName={nameProp} id={id} />
|
||||
</pre>
|
||||
<IndexableDisplayName displayName={nameProp} id={id} />
|
||||
</span>
|
||||
"
|
||||
</Fragment>
|
||||
|
||||
@@ -194,7 +194,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
}
|
||||
|
||||
let strictModeBadge = null;
|
||||
if (element.isStrictModeNonCompliant) {
|
||||
if (element.isStrictModeNonCompliant && element.parentID !== 0) {
|
||||
strictModeBadge = (
|
||||
<Tooltip label="This component is not running in StrictMode. Click to learn more.">
|
||||
<a
|
||||
@@ -237,7 +237,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
<div className={styles.SelectedComponentName}>
|
||||
<div
|
||||
className={
|
||||
element.isStrictModeNonCompliant
|
||||
element.isStrictModeNonCompliant && element.parentID !== 0
|
||||
? `${styles.ComponentName} ${styles.StrictModeNonCompliantComponentName}`
|
||||
: styles.ComponentName
|
||||
}
|
||||
@@ -269,18 +269,21 @@ export default function InspectedElementWrapper(_: Props): React.Node {
|
||||
<ButtonIcon type="error" />
|
||||
</Toggle>
|
||||
)}
|
||||
{canToggleSuspense && (
|
||||
{canToggleSuspense || isSuspended ? (
|
||||
<Toggle
|
||||
isChecked={isSuspended}
|
||||
isDisabled={!canToggleSuspense}
|
||||
onChange={toggleSuspended}
|
||||
title={
|
||||
isSuspended
|
||||
? 'Unsuspend the selected component'
|
||||
? canToggleSuspense
|
||||
? 'Unsuspend the selected component'
|
||||
: 'This boundary is still suspended'
|
||||
: 'Suspend the selected component'
|
||||
}>
|
||||
<ButtonIcon type="suspend" />
|
||||
</Toggle>
|
||||
)}
|
||||
) : null}
|
||||
{store.supportsInspectMatchingDOMElement && (
|
||||
<Button
|
||||
onClick={highlightElement}
|
||||
|
||||
@@ -17,7 +17,11 @@ import NewKeyValue from './NewKeyValue';
|
||||
import {alphaSortEntries, serializeDataForCopy} from '../utils';
|
||||
import Store from '../../store';
|
||||
import styles from './InspectedElementSharedStyles.css';
|
||||
import {ElementTypeClass} from 'react-devtools-shared/src/frontend/types';
|
||||
import {
|
||||
ElementTypeClass,
|
||||
ElementTypeSuspense,
|
||||
ElementTypeActivity,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
|
||||
|
||||
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
|
||||
@@ -50,7 +54,12 @@ export default function InspectedElementPropsTree({
|
||||
const canDeletePaths =
|
||||
type === ElementTypeClass || canEditFunctionPropsDeletePaths;
|
||||
const canEditValues =
|
||||
!readOnly && (type === ElementTypeClass || canEditFunctionProps);
|
||||
!readOnly &&
|
||||
(type === ElementTypeClass || canEditFunctionProps) &&
|
||||
// Make it read-only for Suspense to make it a bit cleaner. It's not
|
||||
// useful to edit children anyway.
|
||||
type !== ElementTypeSuspense &&
|
||||
type !== ElementTypeActivity;
|
||||
const canRenamePaths =
|
||||
type === ElementTypeClass || canEditFunctionPropsRenamePaths;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {useState, useTransition} from 'react';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import KeyValue from './KeyValue';
|
||||
import {serializeDataForCopy} from '../utils';
|
||||
import {serializeDataForCopy, pluralize} from '../utils';
|
||||
import Store from '../../store';
|
||||
import styles from './InspectedElementSharedStyles.css';
|
||||
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
|
||||
@@ -44,6 +44,7 @@ type RowProps = {
|
||||
index: number,
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
skipName?: boolean,
|
||||
};
|
||||
|
||||
function getShortDescription(name: string, description: string): string {
|
||||
@@ -99,6 +100,7 @@ function SuspendedByRow({
|
||||
index,
|
||||
minTime,
|
||||
maxTime,
|
||||
skipName,
|
||||
}: RowProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [openIsPending, startOpenTransition] = useTransition();
|
||||
@@ -166,8 +168,10 @@ function SuspendedByRow({
|
||||
className={styles.CollapsableHeaderIcon}
|
||||
type={isOpen ? 'expanded' : 'collapsed'}
|
||||
/>
|
||||
<span className={styles.CollapsableHeaderTitle}>{name}</span>
|
||||
{shortDescription === '' ? null : (
|
||||
<span className={styles.CollapsableHeaderTitle}>
|
||||
{skipName ? shortDescription : name}
|
||||
</span>
|
||||
{skipName || shortDescription === '' ? null : (
|
||||
<>
|
||||
<span className={styles.CollapsableHeaderSeparator}>{' ('}</span>
|
||||
<span className={styles.CollapsableHeaderTitle}>
|
||||
@@ -300,15 +304,141 @@ type Props = {
|
||||
store: Store,
|
||||
};
|
||||
|
||||
function compareTime(a: SerializedAsyncInfo, b: SerializedAsyncInfo): number {
|
||||
const ioA = a.awaited;
|
||||
const ioB = b.awaited;
|
||||
function withIndex(
|
||||
value: SerializedAsyncInfo,
|
||||
index: number,
|
||||
): {
|
||||
index: number,
|
||||
value: SerializedAsyncInfo,
|
||||
} {
|
||||
return {
|
||||
index,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
function compareTime(
|
||||
a: {
|
||||
index: number,
|
||||
value: SerializedAsyncInfo,
|
||||
},
|
||||
b: {
|
||||
index: number,
|
||||
value: SerializedAsyncInfo,
|
||||
},
|
||||
): number {
|
||||
const ioA = a.value.awaited;
|
||||
const ioB = b.value.awaited;
|
||||
if (ioA.start === ioB.start) {
|
||||
return ioA.end - ioB.end;
|
||||
}
|
||||
return ioA.start - ioB.start;
|
||||
}
|
||||
|
||||
type GroupProps = {
|
||||
bridge: FrontendBridge,
|
||||
element: Element,
|
||||
inspectedElement: InspectedElement,
|
||||
store: Store,
|
||||
name: string,
|
||||
suspendedBy: Array<{
|
||||
index: number,
|
||||
value: SerializedAsyncInfo,
|
||||
}>,
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
};
|
||||
|
||||
function SuspendedByGroup({
|
||||
bridge,
|
||||
element,
|
||||
inspectedElement,
|
||||
store,
|
||||
name,
|
||||
suspendedBy,
|
||||
minTime,
|
||||
maxTime,
|
||||
}: GroupProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
let start = Infinity;
|
||||
let end = -Infinity;
|
||||
let isRejected = false;
|
||||
for (let i = 0; i < suspendedBy.length; i++) {
|
||||
const asyncInfo: SerializedAsyncInfo = suspendedBy[i].value;
|
||||
const ioInfo = asyncInfo.awaited;
|
||||
if (ioInfo.start < start) {
|
||||
start = ioInfo.start;
|
||||
}
|
||||
if (ioInfo.end > end) {
|
||||
end = ioInfo.end;
|
||||
}
|
||||
const value: any = ioInfo.value;
|
||||
if (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
value[meta.name] === 'rejected Thenable'
|
||||
) {
|
||||
isRejected = true;
|
||||
}
|
||||
}
|
||||
const timeScale = 100 / (maxTime - minTime);
|
||||
let left = (start - minTime) * timeScale;
|
||||
let width = (end - start) * timeScale;
|
||||
if (width < 5) {
|
||||
// Use at least a 5% width to avoid showing too small indicators.
|
||||
width = 5;
|
||||
if (left > 95) {
|
||||
left = 95;
|
||||
}
|
||||
}
|
||||
const pluralizedName = pluralize(name);
|
||||
return (
|
||||
<div className={styles.CollapsableRow}>
|
||||
<Button
|
||||
className={styles.CollapsableHeader}
|
||||
onClick={() => {
|
||||
setIsOpen(prevIsOpen => !prevIsOpen);
|
||||
}}
|
||||
title={pluralizedName}>
|
||||
<ButtonIcon
|
||||
className={styles.CollapsableHeaderIcon}
|
||||
type={isOpen ? 'expanded' : 'collapsed'}
|
||||
/>
|
||||
<span className={styles.CollapsableHeaderTitle}>{pluralizedName}</span>
|
||||
<div className={styles.CollapsableHeaderFiller} />
|
||||
{isOpen ? null : (
|
||||
<div className={styles.TimeBarContainer}>
|
||||
<div
|
||||
className={
|
||||
!isRejected ? styles.TimeBarSpan : styles.TimeBarSpanErrored
|
||||
}
|
||||
style={{
|
||||
left: left.toFixed(2) + '%',
|
||||
width: width.toFixed(2) + '%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
{isOpen &&
|
||||
suspendedBy.map(({value, index}) => (
|
||||
<SuspendedByRow
|
||||
key={index}
|
||||
index={index}
|
||||
asyncInfo={value}
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
minTime={minTime}
|
||||
maxTime={maxTime}
|
||||
skipName={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function InspectedElementSuspendedBy({
|
||||
bridge,
|
||||
element,
|
||||
@@ -322,6 +452,17 @@ export default function InspectedElementSuspendedBy({
|
||||
(suspendedBy == null || suspendedBy.length === 0) &&
|
||||
inspectedElement.unknownSuspenders === UNKNOWN_SUSPENDERS_NONE
|
||||
) {
|
||||
if (inspectedElement.isSuspended) {
|
||||
// If we're still suspended, show a place holder until the data loads.
|
||||
// We don't know what we're suspended by until it has loaded.
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>suspended...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -353,9 +494,31 @@ export default function InspectedElementSuspendedBy({
|
||||
minTime = maxTime - 25;
|
||||
}
|
||||
|
||||
const sortedSuspendedBy = suspendedBy === null ? [] : suspendedBy.slice(0);
|
||||
const sortedSuspendedBy =
|
||||
suspendedBy === null ? [] : suspendedBy.map(withIndex);
|
||||
sortedSuspendedBy.sort(compareTime);
|
||||
|
||||
// Organize into groups of consecutive entries with the same name.
|
||||
const groups = [];
|
||||
let currentGroup = null;
|
||||
let currentGroupName = null;
|
||||
for (let i = 0; i < sortedSuspendedBy.length; i++) {
|
||||
const entry = sortedSuspendedBy[i];
|
||||
const name = entry.value.awaited.name;
|
||||
if (
|
||||
currentGroupName !== name ||
|
||||
!name ||
|
||||
name === 'Promise' ||
|
||||
currentGroup === null
|
||||
) {
|
||||
// Create a new group.
|
||||
currentGroupName = name;
|
||||
currentGroup = [];
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
currentGroup.push(entry);
|
||||
}
|
||||
|
||||
let unknownSuspenders = null;
|
||||
switch (inspectedElement.unknownSuspenders) {
|
||||
case UNKNOWN_SUSPENDERS_REASON_PRODUCTION:
|
||||
@@ -396,19 +559,48 @@ export default function InspectedElementSuspendedBy({
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
{sortedSuspendedBy.map((asyncInfo, index) => (
|
||||
<SuspendedByRow
|
||||
key={index}
|
||||
index={index}
|
||||
asyncInfo={asyncInfo}
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
minTime={minTime}
|
||||
maxTime={maxTime}
|
||||
/>
|
||||
))}
|
||||
{groups.length === 1
|
||||
? // If it's only one type of suspender we can flatten it.
|
||||
groups[0].map(entry => (
|
||||
<SuspendedByRow
|
||||
key={entry.index}
|
||||
index={entry.index}
|
||||
asyncInfo={entry.value}
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
minTime={minTime}
|
||||
maxTime={maxTime}
|
||||
/>
|
||||
))
|
||||
: groups.map((entries, index) =>
|
||||
entries.length === 1 ? (
|
||||
<SuspendedByRow
|
||||
key={entries[0].index}
|
||||
index={entries[0].index}
|
||||
asyncInfo={entries[0].value}
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
minTime={minTime}
|
||||
maxTime={maxTime}
|
||||
/>
|
||||
) : (
|
||||
<SuspendedByGroup
|
||||
key={entries[0].index}
|
||||
name={entries[0].value.awaited.name}
|
||||
suspendedBy={entries}
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
minTime={minTime}
|
||||
maxTime={maxTime}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{unknownSuspenders}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>suspense</div>
|
||||
</div>
|
||||
<div className={styles.ToggleSuspenseRow}>
|
||||
<span className={styles.Name}>Suspended</span>
|
||||
{canToggleSuspense ? (
|
||||
// key is required to keep <EditableValue> and header row toggle button in sync
|
||||
<EditableValue
|
||||
key={isSuspended}
|
||||
overrideValue={toggleSuspense}
|
||||
path={['suspense', 'Suspended']}
|
||||
value={isSuspended}
|
||||
/>
|
||||
) : (
|
||||
<span className={styles.Value}>{isSuspended ? 'true' : 'false'}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 = (
|
||||
<div className={styles.InspectedElementSection}>
|
||||
<InspectedElementPropsTree
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={styles.InspectedElement}>
|
||||
@@ -86,22 +101,12 @@ export default function InspectedElementView({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.InspectedElementSection}>
|
||||
<InspectedElementPropsTree
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.InspectedElementSection}>
|
||||
<InspectedElementSuspenseToggle
|
||||
bridge={bridge}
|
||||
inspectedElement={inspectedElement}
|
||||
store={store}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
// For Suspense and Activity we show the props further down.
|
||||
type !== ElementTypeSuspense && type !== ElementTypeActivity
|
||||
? propsSection
|
||||
: null
|
||||
}
|
||||
|
||||
<div className={styles.InspectedElementSection}>
|
||||
<InspectedElementStateTree
|
||||
@@ -166,6 +171,13 @@ export default function InspectedElementView({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
// For Suspense and Activity we show the props below suspended by to give that more priority.
|
||||
type !== ElementTypeSuspense && type !== ElementTypeActivity
|
||||
? null
|
||||
: propsSection
|
||||
}
|
||||
|
||||
{showRenderedBy && (
|
||||
<div
|
||||
className={styles.InspectedElementSection}
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.CallSite, .IgnoredCallSite {
|
||||
.CallSite {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.IgnoredCallSite {
|
||||
opacity: 0.5;
|
||||
.IgnoredCallSite, .BuiltInCallSite {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.CallSite + .BuiltInCallSite {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.Link {
|
||||
|
||||
@@ -60,16 +60,22 @@ export function CallSiteView({
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
|
||||
const ignored =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
|
||||
if (ignored) {
|
||||
// TODO: Make an option to be able to toggle the display of ignore listed rows.
|
||||
// Ideally this UI should be higher than a single Stack Trace so that there's not
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
return null;
|
||||
}
|
||||
// TODO: Make an option to be able to toggle the display of ignore listed rows.
|
||||
// Ideally this UI should be higher than a single Stack Trace so that there's not
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
|
||||
const isBuiltIn = url === '' || url.startsWith('<anonymous>'); // This looks like a fake anonymous through eval.
|
||||
return (
|
||||
<div className={ignored ? styles.IgnoredCallSite : styles.CallSite}>
|
||||
<div
|
||||
className={
|
||||
ignored
|
||||
? styles.IgnoredCallSite
|
||||
: isBuiltIn
|
||||
? styles.BuiltInCallSite
|
||||
: styles.CallSite
|
||||
}>
|
||||
{functionName || virtualFunctionName}
|
||||
{url !== '' && (
|
||||
{!isBuiltIn && (
|
||||
<>
|
||||
{' @ '}
|
||||
<span
|
||||
|
||||
@@ -361,15 +361,17 @@ export default function DevTools({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'suspense'}>
|
||||
<SuspenseTab
|
||||
portalContainer={
|
||||
suspensePortalContainer
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{enableSuspenseTab && (
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'suspense'}>
|
||||
<SuspenseTab
|
||||
portalContainer={
|
||||
suspensePortalContainer
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{editorPortalContainer ? (
|
||||
<EditorPane
|
||||
|
||||
@@ -209,7 +209,6 @@ function updateTree(
|
||||
i++; // Profiling flag
|
||||
i++; // supportsStrictMode flag
|
||||
i++; // hasOwnerMetadata flag
|
||||
i++; // supportsTogglingSuspense flag
|
||||
|
||||
if (__DEBUG__) {
|
||||
debug('Add', `new root fiber ${id}`);
|
||||
|
||||
@@ -64,8 +64,9 @@ export default function SearchInput({
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const {key, metaKey} = event;
|
||||
if (key === 'f' && metaKey) {
|
||||
if (inputRef.current !== null) {
|
||||
inputRef.current.focus();
|
||||
const inputElement = inputRef.current;
|
||||
if (inputElement !== null) {
|
||||
inputElement.focus();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
@@ -75,10 +76,14 @@ export default function SearchInput({
|
||||
// It's important to listen to the ownerDocument to support the browser extension.
|
||||
// Here we use portals to render individual tabs (e.g. Profiler),
|
||||
// and the root document might belong to a different window.
|
||||
const ownerDocument = inputRef.current.ownerDocument;
|
||||
ownerDocument.addEventListener('keydown', handleKeyDown);
|
||||
const ownerDocumentElement = inputRef.current.ownerDocument.documentElement;
|
||||
if (ownerDocumentElement === null) {
|
||||
return;
|
||||
}
|
||||
ownerDocumentElement.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => ownerDocument.removeEventListener('keydown', handleKeyDown);
|
||||
return () =>
|
||||
ownerDocumentElement.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -104,18 +104,6 @@ export default function GeneralSettings(_: {}): React.Node {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className={styles.SettingWrapper}>
|
||||
<div className={styles.RadioLabel}>Display density</div>
|
||||
<select
|
||||
value={displayDensity}
|
||||
onChange={({currentTarget}) =>
|
||||
setDisplayDensity(currentTarget.value)
|
||||
}>
|
||||
<option value="compact">Compact</option>
|
||||
<option value="comfortable">Comfortable</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{supportsTraceUpdates && (
|
||||
<div className={styles.SettingWrapper}>
|
||||
<label className={styles.SettingRow}>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function SuspenseBreadcrumbs(): React$Node {
|
||||
const store = useContext(StoreContext);
|
||||
const treeDispatch = useContext(TreeDispatcherContext);
|
||||
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
|
||||
const {selectedSuspenseID, selectedRootID, lineage} = useContext(
|
||||
const {selectedSuspenseID, lineage, roots} = useContext(
|
||||
SuspenseTreeStateContext,
|
||||
);
|
||||
|
||||
@@ -45,13 +45,13 @@ export default function SuspenseBreadcrumbs(): React$Node {
|
||||
// that rendered the whole screen. In laymans terms this is really "Initial Paint".
|
||||
// TODO: Once we add subtree selection, then the equivalent should be called
|
||||
// "Transition" since in that case it's really about a Transition within the page.
|
||||
selectedRootID !== null ? (
|
||||
roots.length > 0 ? (
|
||||
<li
|
||||
className={styles.SuspenseBreadcrumbsListItem}
|
||||
aria-current={selectedSuspenseID === selectedRootID}>
|
||||
aria-current="true">
|
||||
<button
|
||||
className={styles.SuspenseBreadcrumbsButton}
|
||||
onClick={handleClick.bind(null, selectedRootID)}
|
||||
onClick={handleClick.bind(null, roots[0])}
|
||||
type="button">
|
||||
Initial Paint
|
||||
</button>
|
||||
|
||||
@@ -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'] {
|
||||
|
||||
@@ -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<Rect>((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,51 @@ 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,
|
||||
});
|
||||
}
|
||||
|
||||
function handleDoubleClick(event: SyntheticMouseEvent) {
|
||||
if (event.defaultPrevented) {
|
||||
// Already clicked on an inner rect
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
suspenseTreeDispatch({
|
||||
type: 'SUSPENSE_SET_TIMELINE_INDEX',
|
||||
payload: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const isRootSelected = roots.includes(inspectedElementID);
|
||||
|
||||
return (
|
||||
<div className={styles.SuspenseRectsContainer}>
|
||||
<div
|
||||
className={styles.SuspenseRectsContainer}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
data-highlighted={isRootSelected}>
|
||||
<ViewBox.Provider value={boundingBox}>
|
||||
<div
|
||||
className={styles.SuspenseRectsViewBox}
|
||||
style={{aspectRatio, width}}>
|
||||
{roots.map(rootID => {
|
||||
return <SuspenseRectsShell key={rootID} rootID={rootID} />;
|
||||
return <SuspenseRectsRoot key={rootID} rootID={rootID} />;
|
||||
})}
|
||||
</div>
|
||||
</ViewBox.Provider>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
<div
|
||||
<Tooltip
|
||||
key={index}
|
||||
className={
|
||||
styles.SuspenseScrubberStep +
|
||||
(highlight === index
|
||||
? ' ' + styles.SuspenseScrubberStepHighlight
|
||||
: '')
|
||||
}
|
||||
onPointerDown={handlePress.bind(null, index)}
|
||||
onMouseEnter={onHoverSegment.bind(null, index)}>
|
||||
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'
|
||||
}>
|
||||
<div
|
||||
className={
|
||||
index <= value
|
||||
? styles.SuspenseScrubberBeadSelected
|
||||
: styles.SuspenseScrubberBead
|
||||
styles.SuspenseScrubberStep +
|
||||
(highlight === index
|
||||
? ' ' + styles.SuspenseScrubberStepHighlight
|
||||
: '')
|
||||
}
|
||||
/>
|
||||
</div>,
|
||||
onPointerDown={handlePress.bind(null, index)}
|
||||
onMouseEnter={onHoverSegment.bind(null, index)}>
|
||||
<div
|
||||
className={
|
||||
styles.SuspenseScrubberBead +
|
||||
(index === min
|
||||
? // The first step in the timeline is always a Transition (Initial Paint).
|
||||
// TODO: Support multiple environments.
|
||||
' ' + styles.SuspenseScrubberBeadTransition
|
||||
: '') +
|
||||
(index <= value ? ' ' + styles.SuspenseScrubberBeadSelected : '')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,13 +34,9 @@ import {
|
||||
SuspenseTreeStateContext,
|
||||
} from './SuspenseTreeContext';
|
||||
import {StoreContext, OptionsContext} from '../context';
|
||||
import {TreeDispatcherContext} 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 +67,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 +91,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 && (
|
||||
<select
|
||||
aria-label="Select Suspense Root"
|
||||
className={styles.SuspenseTimelineRootSwitcher}
|
||||
onChange={handleChange}
|
||||
value={selectedRootID === null ? -1 : selectedRootID}>
|
||||
<option disabled={true} value={-1}>
|
||||
----
|
||||
</option>
|
||||
{roots.map(rootID => {
|
||||
// TODO: Use name
|
||||
const name = '#' + rootID;
|
||||
// TODO: Highlight host on hover
|
||||
return (
|
||||
<option key={rootID} value={rootID}>
|
||||
{name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleTreeList({
|
||||
dispatch,
|
||||
state,
|
||||
@@ -427,7 +368,6 @@ function SuspenseTab(_: {}) {
|
||||
<div className={styles.SuspenseBreadcrumbs}>
|
||||
<SuspenseBreadcrumbs />
|
||||
</div>
|
||||
<SelectRoot />
|
||||
<div className={styles.VRule} />
|
||||
<ToggleUniqueSuspenders />
|
||||
{!hideSettings && <SettingsModalContextToggle />}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useContext, useEffect, useRef} from 'react';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {useContext, useEffect} from 'react';
|
||||
import {BridgeContext} from '../context';
|
||||
import {TreeDispatcherContext} from '../Components/TreeContext';
|
||||
import {useHighlightHostInstance, useScrollToHostInstance} from '../hooks';
|
||||
import {
|
||||
@@ -23,20 +23,14 @@ 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, autoScroll} =
|
||||
useContext(SuspenseTreeStateContext);
|
||||
|
||||
const min = 0;
|
||||
const max = timeline.length > 0 ? timeline.length - 1 : 0;
|
||||
@@ -107,47 +101,33 @@ function SuspenseTimelineInput() {
|
||||
});
|
||||
}
|
||||
|
||||
const isInitialMount = useRef(true);
|
||||
// TODO: useEffectEvent here once it's supported in all versions DevTools supports.
|
||||
// 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) {
|
||||
// Skip scrolling on initial mount. Only when we're changing the timeline.
|
||||
isInitialMount.current = false;
|
||||
} else {
|
||||
// When we're scrubbing through the timeline, scroll the current boundary
|
||||
// into view as it was just revealed. This is after we override the milestone
|
||||
// to reveal it.
|
||||
const selectedSuspenseID = timeline[timelineIndex];
|
||||
scrollToHostInstance(selectedSuspenseID);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
changeTimelineIndex(timelineIndex);
|
||||
}, [timelineIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScroll.id > 0) {
|
||||
const scrollToId = autoScroll.id;
|
||||
// Consume the scroll ref so that we only trigger this scroll once.
|
||||
autoScroll.id = 0;
|
||||
scrollToHostInstance(scrollToId);
|
||||
}
|
||||
}, [autoScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!playing) {
|
||||
return undefined;
|
||||
@@ -164,20 +144,6 @@ function SuspenseTimelineInput() {
|
||||
};
|
||||
}, [playing]);
|
||||
|
||||
if (rootID === null) {
|
||||
return (
|
||||
<div className={styles.SuspenseTimelineInput}>No root selected.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!store.supportsTogglingSuspense(rootID)) {
|
||||
return (
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
Can't step through Suspense in production apps.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (timeline.length === 0) {
|
||||
return (
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
@@ -226,10 +192,9 @@ function SuspenseTimelineInput() {
|
||||
}
|
||||
|
||||
export default function SuspenseTimeline(): React$Node {
|
||||
const {selectedRootID} = useContext(SuspenseTreeStateContext);
|
||||
return (
|
||||
<div className={styles.SuspenseTimelineContainer}>
|
||||
<SuspenseTimelineInput key={selectedRootID} />
|
||||
<SuspenseTimelineInput />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user