Compare commits
76 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 | ||
|
|
57d5a59748 | ||
|
|
bc828bf6e3 | ||
|
|
a757cb7667 | ||
|
|
d74f061b69 | ||
|
|
f7254efc5c | ||
|
|
79ca5ae855 |
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 \
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
//
|
||||
// The @latest channel uses the version as-is, e.g.:
|
||||
//
|
||||
// 19.2.0
|
||||
// 19.3.0
|
||||
//
|
||||
// The @canary channel appends additional information, with the scheme
|
||||
// <version>-<label>-<commit_sha>, e.g.:
|
||||
//
|
||||
// 19.2.0-canary-a1c2d3e4
|
||||
// 19.3.0-canary-a1c2d3e4
|
||||
//
|
||||
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.2.0';
|
||||
const ReactVersion = '19.3.0';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
@@ -33,8 +33,8 @@ const canaryChannelLabel = 'canary';
|
||||
const rcNumber = 0;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '6.1.0',
|
||||
'jest-react': '0.17.0',
|
||||
'eslint-plugin-react-hooks': '7.0.0',
|
||||
'jest-react': '0.18.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
'react-dom': ReactVersion,
|
||||
@@ -42,12 +42,12 @@ const stablePackages = {
|
||||
'react-server-dom-turbopack': ReactVersion,
|
||||
'react-server-dom-parcel': ReactVersion,
|
||||
'react-is': ReactVersion,
|
||||
'react-reconciler': '0.33.0',
|
||||
'react-refresh': '0.18.0',
|
||||
'react-reconciler': '0.34.0',
|
||||
'react-refresh': '0.19.0',
|
||||
'react-test-renderer': ReactVersion,
|
||||
'use-subscription': '1.12.0',
|
||||
'use-sync-external-store': '1.6.0',
|
||||
scheduler: '0.27.0',
|
||||
'use-subscription': '1.13.0',
|
||||
'use-sync-external-store': '1.7.0',
|
||||
scheduler: '0.28.0',
|
||||
};
|
||||
|
||||
// These packages do not exist in the @canary or @latest channel, only
|
||||
|
||||
@@ -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,
|
||||
@@ -56,13 +55,14 @@ function AccordionWindowItem({
|
||||
tabsOpen: Set<string>;
|
||||
setTabsOpen: (newTab: Set<string>) => void;
|
||||
hasChanged: boolean;
|
||||
isFailure: boolean;
|
||||
}): React.ReactElement {
|
||||
const id = useId();
|
||||
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';
|
||||
|
||||
@@ -268,11 +271,19 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
|
||||
const [previousOutputKind, setPreviousOutputKind] = useState(
|
||||
compilerOutput.kind,
|
||||
);
|
||||
const isFailure = compilerOutput.kind !== 'ok';
|
||||
|
||||
if (compilerOutput.kind !== previousOutputKind) {
|
||||
setPreviousOutputKind(compilerOutput.kind);
|
||||
setTabsOpen(new Set(['Output']));
|
||||
setActiveTab('Output');
|
||||
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) {
|
||||
@@ -375,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,
|
||||
};
|
||||
|
||||
@@ -49,6 +49,8 @@ export default function TabbedWindow({
|
||||
{isActive && (
|
||||
<ViewTransition
|
||||
name={transitionName}
|
||||
enter={{default: 'none'}}
|
||||
exit={{default: 'none'}}
|
||||
share={{
|
||||
[TOGGLE_TAB_TRANSITION]: 'tab-highlight',
|
||||
default: 'none',
|
||||
@@ -58,6 +60,8 @@ export default function TabbedWindow({
|
||||
</ViewTransition>
|
||||
)}
|
||||
<ViewTransition
|
||||
enter={{default: 'none'}}
|
||||
exit={{default: 'none'}}
|
||||
update={{
|
||||
[TOGGLE_TAB_TRANSITION]: 'tab-text',
|
||||
default: 'none',
|
||||
|
||||
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: {
|
||||
|
||||
@@ -454,6 +454,32 @@ function collectNonNullsInBlocks(
|
||||
assumedNonNullObjects.add(entry);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
fn.env.config.enablePreserveExistingMemoizationGuarantees &&
|
||||
instr.value.kind === 'StartMemoize' &&
|
||||
instr.value.deps != null
|
||||
) {
|
||||
for (const dep of instr.value.deps) {
|
||||
if (dep.root.kind === 'NamedLocal') {
|
||||
if (
|
||||
!isImmutableAtInstr(dep.root.value.identifier, instr.id, context)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < dep.path.length; i++) {
|
||||
const pathEntry = dep.path[i]!;
|
||||
if (pathEntry.optional) {
|
||||
break;
|
||||
}
|
||||
const depNode = context.registry.getOrCreateProperty({
|
||||
identifier: dep.root.value.identifier,
|
||||
path: dep.path.slice(0, i),
|
||||
reactive: dep.root.value.reactive,
|
||||
});
|
||||
assumedNonNullObjects.add(depNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -210,7 +210,7 @@ export const EnvironmentConfigSchema = z.object({
|
||||
* that if a useEffect or useCallback references a function value, that function value will be
|
||||
* considered frozen, and in turn all of its referenced variables will be considered frozen as well.
|
||||
*/
|
||||
enablePreserveExistingMemoizationGuarantees: z.boolean().default(false),
|
||||
enablePreserveExistingMemoizationGuarantees: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Validates that all useMemo/useCallback values are also memoized by Forget. This mode can be
|
||||
@@ -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"."`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
// bar(props.b) is an allocating expression that produces a primitive, which means
|
||||
// that Forget should memoize it.
|
||||
// Correctness:
|
||||
@@ -16,7 +17,8 @@ function AllocatingPrimitiveAsDep(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // bar(props.b) is an allocating expression that produces a primitive, which means
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
// bar(props.b) is an allocating expression that produces a primitive, which means
|
||||
// that Forget should memoize it.
|
||||
// Correctness:
|
||||
// - y depends on either bar(props.b) or bar(props.b) + 1
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
// bar(props.b) is an allocating expression that produces a primitive, which means
|
||||
// that Forget should memoize it.
|
||||
// Correctness:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
const someGlobal = {value: 0};
|
||||
@@ -32,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
|
||||
const someGlobal = { value: 0 };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
const someGlobal = {value: 0};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
let a = foo();
|
||||
// freeze `a` so we know the next line cannot mutate it
|
||||
@@ -17,7 +18,7 @@ function Component(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const a = foo();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
let a = foo();
|
||||
// freeze `a` so we know the next line cannot mutate it
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {Stringify, identity} from 'shared-runtime';
|
||||
|
||||
function foo() {
|
||||
@@ -64,7 +65,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { Stringify, identity } from "shared-runtime";
|
||||
|
||||
function foo() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {Stringify, identity} from 'shared-runtime';
|
||||
|
||||
function foo() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
@@ -25,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function foo(props) {
|
||||
let x, y;
|
||||
({x, y} = {x: props.a, y: props.b});
|
||||
@@ -21,6 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function foo(props) {
|
||||
let x;
|
||||
let y;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function foo(props) {
|
||||
let x, y;
|
||||
({x, y} = {x: props.a, y: props.b});
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @enableNewMutationAliasingModel
|
||||
// @flow @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
/**
|
||||
* This hook returns a function that when called with an input object,
|
||||
* will return the result of mapping that input with the supplied map
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow @enableNewMutationAliasingModel
|
||||
// @flow @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
/**
|
||||
* This hook returns a function that when called with an input object,
|
||||
* will return the result of mapping that input with the supplied map
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
import {identity, Stringify, useHook} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
import {identity, Stringify, useHook} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
// This is a bug in our dependency inference: we stop capturing dependencies
|
||||
// after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
|
||||
// was non-nullish, then we can access `.c.d?.e`. Thus we should take the
|
||||
// full property chain, exactly as-is with optionals/non-optionals, as a
|
||||
// dependency
|
||||
return identity(x.a.b?.c.d?.e);
|
||||
},
|
||||
});
|
||||
}, [x.a.b?.c.d?.e]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <Inner x={x} result={result} />;
|
||||
}
|
||||
|
||||
function Inner({x, result}) {
|
||||
'use no memo';
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.a.b?.c`, but the source dependencies were [x.a.b?.c.d?.e]. Inferred less specific property than source.
|
||||
|
||||
error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.ts:7:25
|
||||
5 |
|
||||
6 | function Component({x}) {
|
||||
> 7 | const object = useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 8 | return identity({
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 9 | callback: () => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 10 | // This is a bug in our dependency inference: we stop capturing dependencies
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 11 | // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 12 | // was non-nullish, then we can access `.c.d?.e`. Thus we should take the
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 13 | // full property chain, exactly as-is with optionals/non-optionals, as a
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 14 | // dependency
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 15 | return identity(x.a.b?.c.d?.e);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 16 | },
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 17 | });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 18 | }, [x.a.b?.c.d?.e]);
|
||||
| ^^^^ Could not preserve existing manual memoization
|
||||
19 | const result = useMemo(() => {
|
||||
20 | return [object.callback()];
|
||||
21 | }, [object]);
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
// This is a bug in our dependency inference: we stop capturing dependencies
|
||||
// after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
|
||||
// was non-nullish, then we can access `.c.d?.e`. Thus we should take the
|
||||
// full property chain, exactly as-is with optionals/non-optionals, as a
|
||||
// dependency
|
||||
return identity(x.a.b?.c.d?.e);
|
||||
},
|
||||
});
|
||||
}, [x.a.b?.c.d?.e]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <Inner x={x} result={result} />;
|
||||
}
|
||||
|
||||
function Inner({x, result}) {
|
||||
'use no memo';
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {logValue, useFragment, useHook, typedLog} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useFragment} from 'react-relay';
|
||||
import LogEvent from 'LogEvent';
|
||||
import {useCallback, useMemo} from 'react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees
|
||||
// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useFragment} from 'react-relay';
|
||||
import LogEvent from 'LogEvent';
|
||||
import {useCallback, useMemo} from 'react';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {ValidateMemoization, useHook} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {ValidateMemoization, useHook} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {makeObject_Primitives, Stringify} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {makeObject_Primitives, Stringify} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {makeObject_Primitives, Stringify} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {makeObject_Primitives, Stringify} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo, useState} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
@@ -28,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c2 } from "react/compiler-runtime";
|
||||
import { c as _c2 } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo, useState } from "react";
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo, useState} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import fbt from 'fbt';
|
||||
|
||||
/**
|
||||
@@ -35,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import fbt from "fbt";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import fbt from 'fbt';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
const {identity, mutate} = require('shared-runtime');
|
||||
|
||||
function Component(props) {
|
||||
@@ -24,6 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
const { identity, mutate } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
const {identity, mutate} = require('shared-runtime');
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @hookPattern:".*\b(use[^$]+)$"
|
||||
// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
import * as React from 'react';
|
||||
import {makeArray, useHook} from 'shared-runtime';
|
||||
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @hookPattern:".*\b(use[^$]+)$"
|
||||
import { c as _c } from "react/compiler-runtime"; // @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
import * as React from "react";
|
||||
import { makeArray, useHook } from "shared-runtime";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @hookPattern:".*\b(use[^$]+)$"
|
||||
// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false
|
||||
|
||||
import * as React from 'react';
|
||||
import {makeArray, useHook} from 'shared-runtime';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @debug
|
||||
// @debug @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x[props.value];
|
||||
@@ -14,7 +14,7 @@ function Component(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @debug
|
||||
// @debug @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x[props.value];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @debug
|
||||
// @debug @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x[props.value];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x.value;
|
||||
@@ -13,6 +14,7 @@ function Component(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x.value;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
function Component(props) {
|
||||
const x = makeObject();
|
||||
const y = delete x.value;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
@@ -32,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
@@ -29,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
mutate,
|
||||
@@ -49,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
mutate,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
mutate,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
@@ -41,7 +42,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
@@ -42,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
@@ -40,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
@@ -36,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
@@ -40,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
typedCapture,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function useFoo(arr1, arr2) {
|
||||
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
|
||||
function useFoo(arr1, arr2) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableNewMutationAliasingModel
|
||||
// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function useFoo(arr1, arr2) {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x.y.z); // accesses more levels of properties than the manual memo
|
||||
},
|
||||
});
|
||||
// x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable
|
||||
// we can only take a dep on x.y, not x.y.z
|
||||
}, [x.y]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <ValidateMemoization inputs={[x.y]} output={result} />;
|
||||
}
|
||||
|
||||
const input1 = {x: {y: {z: 42}}};
|
||||
const input1b = {x: {y: {z: 42}}};
|
||||
const input2 = {x: {y: {z: 3.14}}};
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [input1],
|
||||
sequentialRenders: [
|
||||
input1,
|
||||
input1,
|
||||
input1b, // should reset even though .z didn't change
|
||||
input1,
|
||||
input2,
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(11);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x.y) {
|
||||
t1 = identity({ callback: () => identity(x.y.z) });
|
||||
$[0] = x.y;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const object = t1;
|
||||
let t2;
|
||||
if ($[2] !== object) {
|
||||
t2 = object.callback();
|
||||
$[2] = object;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t2) {
|
||||
t3 = [t2];
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
const result = t3;
|
||||
let t4;
|
||||
if ($[6] !== x.y) {
|
||||
t4 = [x.y];
|
||||
$[6] = x.y;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== result || $[9] !== t4) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={result} />;
|
||||
$[8] = result;
|
||||
$[9] = t4;
|
||||
$[10] = t5;
|
||||
} else {
|
||||
t5 = $[10];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
const input1 = { x: { y: { z: 42 } } };
|
||||
const input1b = { x: { y: { z: 42 } } };
|
||||
const input2 = { x: { y: { z: 3.14 } } };
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [input1],
|
||||
sequentialRenders: [
|
||||
input1,
|
||||
input1,
|
||||
input1b, // should reset even though .z didn't change
|
||||
input1,
|
||||
input2,
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[{"z":42}],"output":[42]}</div>
|
||||
<div>{"inputs":[{"z":42}],"output":[42]}</div>
|
||||
<div>{"inputs":[{"z":42}],"output":[42]}</div>
|
||||
<div>{"inputs":[{"z":42}],"output":[42]}</div>
|
||||
<div>{"inputs":[{"z":3.14}],"output":[3.14]}</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x.y.z); // accesses more levels of properties than the manual memo
|
||||
},
|
||||
});
|
||||
// x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable
|
||||
// we can only take a dep on x.y, not x.y.z
|
||||
}, [x.y]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <ValidateMemoization inputs={[x.y]} output={result} />;
|
||||
}
|
||||
|
||||
const input1 = {x: {y: {z: 42}}};
|
||||
const input1b = {x: {y: {z: 42}}};
|
||||
const input2 = {x: {y: {z: 3.14}}};
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [input1],
|
||||
sequentialRenders: [
|
||||
input1,
|
||||
input1,
|
||||
input1b, // should reset even though .z didn't change
|
||||
input1,
|
||||
input2,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x.y.z);
|
||||
},
|
||||
});
|
||||
}, [x.y.z]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(11);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
if ($[0] !== x.y.z) {
|
||||
t1 = identity({ callback: () => identity(x.y.z) });
|
||||
$[0] = x.y.z;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const object = t1;
|
||||
let t2;
|
||||
if ($[2] !== object) {
|
||||
t2 = object.callback();
|
||||
$[2] = object;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t2) {
|
||||
t3 = [t2];
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
const result = t3;
|
||||
let t4;
|
||||
if ($[6] !== x.y.z) {
|
||||
t4 = [x.y.z];
|
||||
$[6] = x.y.z;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== result || $[9] !== t4) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={result} />;
|
||||
$[8] = result;
|
||||
$[9] = t4;
|
||||
$[10] = t5;
|
||||
} else {
|
||||
t5 = $[10];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: { y: { z: 42 } } }],
|
||||
sequentialRenders: [
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 3.14 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 3.14 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[42],"output":[42]}</div>
|
||||
<div>{"inputs":[42],"output":[42]}</div>
|
||||
<div>{"inputs":[3.14],"output":[3.14]}</div>
|
||||
<div>{"inputs":[42],"output":[42]}</div>
|
||||
<div>{"inputs":[3.14],"output":[3.14]}</div>
|
||||
<div>{"inputs":[42],"output":[42]}</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x.y.z);
|
||||
},
|
||||
});
|
||||
}, [x.y.z]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x?.y?.z, y.a?.b, z.a.b?.c);
|
||||
},
|
||||
});
|
||||
}, [x?.y?.z, y.a?.b, z.a.b?.c]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <Inner x={x} result={result} />;
|
||||
}
|
||||
|
||||
function Inner({x, result}) {
|
||||
'use no memo';
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(11);
|
||||
const { x, y, z } = t0;
|
||||
|
||||
x?.y?.z;
|
||||
y.a?.b;
|
||||
z.a.b?.c;
|
||||
let t1;
|
||||
if ($[0] !== x?.y?.z || $[1] !== y.a?.b || $[2] !== z.a.b?.c) {
|
||||
t1 = identity({ callback: () => identity(x?.y?.z, y.a?.b, z.a.b?.c) });
|
||||
$[0] = x?.y?.z;
|
||||
$[1] = y.a?.b;
|
||||
$[2] = z.a.b?.c;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const object = t1;
|
||||
let t2;
|
||||
if ($[4] !== object) {
|
||||
t2 = object.callback();
|
||||
$[4] = object;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
let t3;
|
||||
if ($[6] !== t2) {
|
||||
t3 = [t2];
|
||||
$[6] = t2;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
const result = t3;
|
||||
let t4;
|
||||
if ($[8] !== result || $[9] !== x) {
|
||||
t4 = <Inner x={x} result={result} />;
|
||||
$[8] = result;
|
||||
$[9] = x;
|
||||
$[10] = t4;
|
||||
} else {
|
||||
t4 = $[10];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
function Inner({ x, result }) {
|
||||
"use no memo";
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: { y: { z: 42 } } }],
|
||||
sequentialRenders: [
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 3.14 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
{ x: { y: { z: 3.14 } } },
|
||||
{ x: { y: { z: 42 } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
|
||||
@@ -0,0 +1,36 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
const object = useMemo(() => {
|
||||
return identity({
|
||||
callback: () => {
|
||||
return identity(x?.y?.z, y.a?.b, z.a.b?.c);
|
||||
},
|
||||
});
|
||||
}, [x?.y?.z, y.a?.b, z.a.b?.c]);
|
||||
const result = useMemo(() => {
|
||||
return [object.callback()];
|
||||
}, [object]);
|
||||
return <Inner x={x} result={result} />;
|
||||
}
|
||||
|
||||
function Inner({x, result}) {
|
||||
'use no memo';
|
||||
return <ValidateMemoization inputs={[x.y.z]} output={result} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: {y: {z: 42}}}],
|
||||
sequentialRenders: [
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
{x: {y: {z: 3.14}}},
|
||||
{x: {y: {z: 42}}},
|
||||
],
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useCallback} from 'react';
|
||||
|
||||
function Component({propA, propB}) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user