Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
f263ff7e0d [compiler] Cleanup diagnostic messages
Minor sytlistic cleanup
2025-07-24 15:44:24 -07:00
35 changed files with 191 additions and 689 deletions

View File

@@ -1,5 +1,5 @@
import { c as _c } from "react/compiler-runtime"; // 
@compilationMode:"all"
        @compilationMode:"all"
function nonReactFn() {
  const $ = _c(1);
  let t0;

View File

@@ -11,7 +11,6 @@ import * as t from '@babel/types';
import BabelPluginReactCompiler, {
CompilerError,
CompilerErrorDetail,
CompilerDiagnostic,
Effect,
ErrorSeverity,
parseConfigPragmaForTests,
@@ -142,13 +141,10 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
],
];
function compile(
source: string,
mode: 'compiler' | 'linter',
): [CompilerOutput, 'flow' | 'typescript'] {
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const otherErrors: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
const otherErrors: Array<CompilerErrorDetail> = [];
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
@@ -207,22 +203,6 @@ function compile(
};
const parsedOptions = 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,
}
: {
/* use defaults for compiler mode */
},
});
const opts: PluginOptions = parsePluginOptions({
...parsedOptions,
@@ -234,7 +214,7 @@ function compile(
debugLogIRs: logIR,
logEvent: (_filename: string | null, event: LoggerEvent) => {
if (event.kind === 'CompileError') {
otherErrors.push(event.detail);
otherErrors.push(new CompilerErrorDetail(event.detail));
}
},
},
@@ -246,7 +226,7 @@ function compile(
* (i.e. object shape that is not CompilerError)
*/
if (err instanceof CompilerError && err.details.length > 0) {
error.merge(err);
error.details.push(...err.details);
} else {
/**
* Handle unexpected failures by logging (to get a stack trace)
@@ -265,15 +245,12 @@ function compile(
}
// Only include logger errors if there weren't other errors
if (!error.hasErrors() && otherErrors.length !== 0) {
otherErrors.forEach(e => error.details.push(e));
otherErrors.forEach(e => error.push(e));
}
if (error.hasErrors()) {
return [{kind: 'err', results, error}, language];
return [{kind: 'err', results, error: error}, language];
}
return [
{kind: 'ok', results, transformOutput, errors: error.details},
language,
];
return [{kind: 'ok', results, transformOutput}, language];
}
export default function Editor(): JSX.Element {
@@ -282,11 +259,7 @@ export default function Editor(): JSX.Element {
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar} = useSnackbar();
const [compilerOutput, language] = useMemo(
() => compile(deferredStore.source, 'compiler'),
[deferredStore.source],
);
const [linterOutput] = useMemo(
() => compile(deferredStore.source, 'linter'),
() => compile(deferredStore.source),
[deferredStore.source],
);
@@ -312,26 +285,19 @@ export default function Editor(): JSX.Element {
});
});
let mergedOutput: CompilerOutput;
let errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
if (compilerOutput.kind === 'ok') {
errors = linterOutput.kind === 'ok' ? [] : linterOutput.error.details;
mergedOutput = {
...compilerOutput,
errors,
};
} else {
mergedOutput = compilerOutput;
errors = compilerOutput.error.details;
}
return (
<>
<div className="relative flex basis top-14">
<div className={clsx('relative sm:basis-1/4')}>
<Input language={language} errors={errors} />
<Input
language={language}
errors={
compilerOutput.kind === 'err' ? compilerOutput.error.details : []
}
/>
</div>
<div className={clsx('flex sm:flex flex-wrap')}>
<Output store={deferredStore} compilerOutput={mergedOutput} />
<Output store={deferredStore} compilerOutput={compilerOutput} />
</div>
</div>
</>

View File

@@ -36,18 +36,13 @@ export default function Input({errors, language}: Props): JSX.Element {
const uri = monaco.Uri.parse(`file:///index.js`);
const model = monaco.editor.getModel(uri);
invariant(model, 'Model must exist for the selected input file.');
renderReactCompilerMarkers({
monaco,
model,
details: errors,
source: store.source,
});
renderReactCompilerMarkers({monaco, model, details: errors});
/**
* N.B. that `tabSize` is a model property, not an editor property.
* So, the tab size has to be set per model.
*/
model.updateOptions({tabSize: 2});
}, [monaco, errors, store.source]);
}, [monaco, errors]);
useEffect(() => {
/**

View File

@@ -11,11 +11,7 @@ import {
InformationCircleIcon,
} from '@heroicons/react/outline';
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
import {
CompilerErrorDetail,
CompilerDiagnostic,
type CompilerError,
} from 'babel-plugin-react-compiler';
import {type CompilerError} from 'babel-plugin-react-compiler';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
@@ -48,7 +44,6 @@ export type CompilerOutput =
kind: 'ok';
transformOutput: CompilerTransformOutput;
results: Map<string, Array<PrintedCompilerPipelineValue>>;
errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
}
| {
kind: 'err';
@@ -128,36 +123,10 @@ async function tabify(
parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
plugins: [parserBabel, prettierPluginEstree],
});
let output: string;
let language: string;
if (compilerOutput.errors.length === 0) {
output = code;
language = 'javascript';
} else {
language = 'markdown';
output = `
# Summary
React Compiler compiled this function successfully, but there are lint errors that indicate potential issues with the original code.
## ${compilerOutput.errors.length} Lint Errors
${compilerOutput.errors.map(e => e.printErrorMessage(source, {eslint: false})).join('\n\n')}
## Output
\`\`\`js
${code}
\`\`\`
`.trim();
}
reorderedTabs.set(
'Output',
'JS',
<TextTabContent
output={output}
language={language}
output={code}
diff={null}
showInfoPanel={false}></TextTabContent>,
);
@@ -173,18 +142,6 @@ ${code}
</>,
);
}
} else if (compilerOutput.kind === 'err') {
const errors = compilerOutput.error.printErrorMessage(source, {
eslint: false,
});
reorderedTabs.set(
'Output',
<TextTabContent
output={errors}
language="markdown"
diff={null}
showInfoPanel={false}></TextTabContent>,
);
}
tabs.forEach((tab, name) => {
reorderedTabs.set(name, tab);
@@ -205,32 +162,17 @@ function getSourceMapUrl(code: string, map: string): string | null {
}
function Output({store, compilerOutput}: Props): JSX.Element {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(
() => new Set(['Output']),
);
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map(),
);
/*
* Update the active tab back to the output or errors tab when the compilation state
* changes between success/failure.
*/
const [previousOutputKind, setPreviousOutputKind] = useState(
compilerOutput.kind,
);
if (compilerOutput.kind !== previousOutputKind) {
setPreviousOutputKind(compilerOutput.kind);
setTabsOpen(new Set(['Output']));
}
useEffect(() => {
tabify(store.source, compilerOutput).then(tabs => {
setTabs(tabs);
});
}, [store.source, compilerOutput]);
const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
const changedPasses: Set<string> = new Set(['JS', 'HIR']); // Initial and final passes should always be bold
let lastResult: string = '';
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
@@ -254,6 +196,20 @@ function Output({store, compilerOutput}: Props): JSX.Element {
tabs={tabs}
changedPasses={changedPasses}
/>
{compilerOutput.kind === 'err' ? (
<div
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
style={{width: 'calc(100vw - 650px)'}}>
<div className="w-full p-4 basis-full border-b">
<h2>COMPILER ERRORS</h2>
</div>
<pre
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
style={{width: 'calc(100vw - 650px)', height: '150px'}}>
<code>{compilerOutput.error.toString()}</code>
</pre>
</div>
) : null}
</>
);
}
@@ -262,12 +218,10 @@ function TextTabContent({
output,
diff,
showInfoPanel,
language,
}: {
output: string;
diff: string | null;
showInfoPanel: boolean;
language: string;
}): JSX.Element {
const [diffMode, setDiffMode] = useState(false);
return (
@@ -318,7 +272,7 @@ function TextTabContent({
/>
) : (
<MonacoEditor
language={language ?? 'javascript'}
defaultLanguage="javascript"
value={output}
options={{
...monacoOptions,

View File

@@ -28,5 +28,5 @@ export const monacoOptions: Partial<EditorProps['options']> = {
automaticLayout: true,
wordWrap: 'on',
wrappingIndent: 'same',
wrappingIndent: 'deepIndent',
};

View File

@@ -6,11 +6,7 @@
*/
import {Monaco} from '@monaco-editor/react';
import {
CompilerDiagnostic,
CompilerErrorDetail,
ErrorSeverity,
} from 'babel-plugin-react-compiler';
import {CompilerErrorDetail, ErrorSeverity} from 'babel-plugin-react-compiler';
import {MarkerSeverity, type editor} from 'monaco-editor';
function mapReactCompilerSeverityToMonaco(
@@ -26,46 +22,38 @@ function mapReactCompilerSeverityToMonaco(
}
function mapReactCompilerDiagnosticToMonacoMarker(
detail: CompilerErrorDetail | CompilerDiagnostic,
detail: CompilerErrorDetail,
monaco: Monaco,
source: string,
): editor.IMarkerData | null {
const loc = detail.primaryLocation();
if (loc == null || typeof loc === 'symbol') {
if (detail.loc == null || typeof detail.loc === 'symbol') {
return null;
}
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
let message = detail.printErrorMessage(source, {eslint: true});
let message = detail.printErrorMessage();
return {
severity,
message,
startLineNumber: loc.start.line,
startColumn: loc.start.column + 1,
endLineNumber: loc.end.line,
endColumn: loc.end.column + 1,
startLineNumber: detail.loc.start.line,
startColumn: detail.loc.start.column + 1,
endLineNumber: detail.loc.end.line,
endColumn: detail.loc.end.column + 1,
};
}
type ReactCompilerMarkerConfig = {
monaco: Monaco;
model: editor.ITextModel;
details: Array<CompilerErrorDetail | CompilerDiagnostic>;
source: string;
details: Array<CompilerErrorDetail>;
};
let decorations: Array<string> = [];
export function renderReactCompilerMarkers({
monaco,
model,
details,
source,
}: ReactCompilerMarkerConfig): void {
const markers: Array<editor.IMarkerData> = [];
for (const detail of details) {
const marker = mapReactCompilerDiagnosticToMonacoMarker(
detail,
monaco,
source,
);
const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
if (marker == null) {
continue;
}

View File

@@ -84,7 +84,9 @@ export default function BabelPluginReactCompiler(
}
} catch (e) {
if (e instanceof CompilerError) {
throw e.withPrintedMessage(pass.file.code, {eslint: false});
throw new Error(
e.printErrorMessage(pass.file.code, {eslint: false}),
);
}
throw e;
}

View File

@@ -262,7 +262,6 @@ export class CompilerErrorDetail {
export class CompilerError extends Error {
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
printedMessage: string | null = null;
static invariant(
condition: unknown,
@@ -348,29 +347,18 @@ export class CompilerError extends Error {
}
override get message(): string {
return this.printedMessage ?? this.toString();
return this.toString();
}
override set message(_message: string) {}
override toString(): string {
if (this.printedMessage) {
return this.printedMessage;
}
if (Array.isArray(this.details)) {
return this.details.map(detail => detail.toString()).join('\n\n');
}
return this.name;
}
withPrintedMessage(
source: string,
options: PrintErrorMessageOptions,
): CompilerError {
this.printedMessage = this.printErrorMessage(source, options);
return this;
}
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
if (options.eslint && this.details.length === 1) {
return this.details[0].printErrorMessage(source, options);

View File

@@ -8,8 +8,8 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
CompilerSuggestionOperation,
ErrorSeverity,
} from '../CompilerError';
@@ -181,11 +181,12 @@ export function suppressionsToCompilerError(
'Unhandled suppression source',
);
}
error.pushDiagnostic(
CompilerDiagnostic.create({
category: reason,
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `${reason}. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior`,
description: suppressionRange.disableComment.value.trim(),
severity: ErrorSeverity.InvalidReact,
loc: suppressionRange.disableComment.loc ?? null,
suggestions: [
{
description: suggestion,
@@ -196,10 +197,6 @@ export function suppressionsToCompilerError(
op: CompilerSuggestionOperation.Remove,
},
],
}).withDetail({
kind: 'error',
loc: suppressionRange.disableComment.loc ?? null,
message: 'Found React rule suppression',
}),
);
}

View File

@@ -943,10 +943,7 @@ export function printAliasingEffect(effect: AliasingEffect): string {
return `Assign ${printPlaceForAliasEffect(effect.into)} = ${printPlaceForAliasEffect(effect.from)}`;
}
case 'Alias': {
return `Alias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
}
case 'MaybeAlias': {
return `MaybeAlias ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;
return `Alias ${printPlaceForAliasEffect(effect.into)} = ${printPlaceForAliasEffect(effect.from)}`;
}
case 'Capture': {
return `Capture ${printPlaceForAliasEffect(effect.into)} <- ${printPlaceForAliasEffect(effect.from)}`;

View File

@@ -90,23 +90,6 @@ export type AliasingEffect =
* c could be mutating a.
*/
| {kind: 'Alias'; from: Place; into: Place}
/**
* Indicates the potential for information flow from `from` to `into`. This is used for a specific
* case: functions with unknown signatures. If the compiler sees a call such as `foo(x)`, it has to
* consider several possibilities (which may depend on the arguments):
* - foo(x) returns a new mutable value that does not capture any information from x.
* - foo(x) returns a new mutable value that *does* capture information from x.
* - foo(x) returns x itself, ie foo is the identity function
*
* The same is true of functions that take multiple arguments: `cond(a, b, c)` could conditionally
* return b or c depending on the value of a.
*
* To represent this case, MaybeAlias represents the fact that an aliasing relationship could exist.
* Any mutations that flow through this relationship automatically become conditional.
*/
| {kind: 'MaybeAlias'; from: Place; into: Place}
/**
* Records direct assignment: `into = from`.
*/
@@ -200,8 +183,7 @@ export function hashEffect(effect: AliasingEffect): string {
case 'ImmutableCapture':
case 'Assign':
case 'Alias':
case 'Capture':
case 'MaybeAlias': {
case 'Capture': {
return [
effect.kind,
effect.from.identifier.id,

View File

@@ -85,8 +85,7 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
case 'Assign':
case 'Alias':
case 'Capture':
case 'CreateFrom':
case 'MaybeAlias': {
case 'CreateFrom': {
capturedOrMutated.add(effect.from.identifier.id);
break;
}

View File

@@ -691,7 +691,6 @@ function applyEffect(
}
break;
}
case 'MaybeAlias':
case 'Alias':
case 'Capture': {
CompilerError.invariant(
@@ -956,7 +955,7 @@ function applyEffect(
context,
state,
// OK: recording information flow
{kind: 'MaybeAlias', from: operand, into: effect.into},
{kind: 'Alias', from: operand, into: effect.into},
initialized,
effects,
);
@@ -1324,7 +1323,7 @@ class InferenceState {
return 'mutate-global';
}
case ValueKind.MaybeFrozen: {
return 'mutate-frozen';
return 'none';
}
default: {
assertExhaustive(kind, `Unexpected kind ${kind}`);
@@ -2377,7 +2376,6 @@ function computeEffectsForSignature(
// Apply substitutions
for (const effect of signature.effects) {
switch (effect.kind) {
case 'MaybeAlias':
case 'Assign':
case 'ImmutableCapture':
case 'Alias':

View File

@@ -160,8 +160,6 @@ export function inferMutationAliasingRanges(
state.assign(index++, effect.from, effect.into);
} else if (effect.kind === 'Alias') {
state.assign(index++, effect.from, effect.into);
} else if (effect.kind === 'MaybeAlias') {
state.maybeAlias(index++, effect.from, effect.into);
} else if (effect.kind === 'Capture') {
state.capture(index++, effect.from, effect.into);
} else if (
@@ -348,8 +346,7 @@ export function inferMutationAliasingRanges(
case 'Assign':
case 'Alias':
case 'Capture':
case 'CreateFrom':
case 'MaybeAlias': {
case 'CreateFrom': {
const isMutatedOrReassigned =
effect.into.identifier.mutableRange.end > instr.id;
if (isMutatedOrReassigned) {
@@ -570,12 +567,7 @@ type Node = {
createdFrom: Map<Identifier, number>;
captures: Map<Identifier, number>;
aliases: Map<Identifier, number>;
maybeAliases: Map<Identifier, number>;
edges: Array<{
index: number;
node: Identifier;
kind: 'capture' | 'alias' | 'maybeAlias';
}>;
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
transitive: {kind: MutationKind; loc: SourceLocation} | null;
local: {kind: MutationKind; loc: SourceLocation} | null;
lastMutated: number;
@@ -593,7 +585,6 @@ class AliasingState {
createdFrom: new Map(),
captures: new Map(),
aliases: new Map(),
maybeAliases: new Map(),
edges: [],
transitive: null,
local: null,
@@ -639,18 +630,6 @@ class AliasingState {
}
}
maybeAlias(index: number, from: Place, into: Place): void {
const fromNode = this.nodes.get(from.identifier);
const toNode = this.nodes.get(into.identifier);
if (fromNode == null || toNode == null) {
return;
}
fromNode.edges.push({index, node: into.identifier, kind: 'maybeAlias'});
if (!toNode.maybeAliases.has(from.identifier)) {
toNode.maybeAliases.set(from.identifier, index);
}
}
render(index: number, start: Identifier, errors: CompilerError): void {
const seen = new Set<Identifier>();
const queue: Array<Identifier> = [start];
@@ -694,24 +673,22 @@ class AliasingState {
// Null is used for simulated mutations
end: InstructionId | null,
transitive: boolean,
startKind: MutationKind,
kind: MutationKind,
loc: SourceLocation,
errors: CompilerError,
): void {
const seen = new Map<Identifier, MutationKind>();
const seen = new Set<Identifier>();
const queue: Array<{
place: Identifier;
transitive: boolean;
direction: 'backwards' | 'forwards';
kind: MutationKind;
}> = [{place: start, transitive, direction: 'backwards', kind: startKind}];
}> = [{place: start, transitive, direction: 'backwards'}];
while (queue.length !== 0) {
const {place: current, transitive, direction, kind} = queue.pop()!;
const previousKind = seen.get(current);
if (previousKind != null && previousKind >= kind) {
const {place: current, transitive, direction} = queue.pop()!;
if (seen.has(current)) {
continue;
}
seen.set(current, kind);
seen.add(current);
const node = this.nodes.get(current);
if (node == null) {
continue;
@@ -747,18 +724,13 @@ class AliasingState {
if (edge.index >= index) {
break;
}
queue.push({place: edge.node, transitive, direction: 'forwards', kind});
queue.push({place: edge.node, transitive, direction: 'forwards'});
}
for (const [alias, when] of node.createdFrom) {
if (when >= index) {
continue;
}
queue.push({
place: alias,
transitive: true,
direction: 'backwards',
kind,
});
queue.push({place: alias, transitive: true, direction: 'backwards'});
}
if (direction === 'backwards' || node.value.kind !== 'Phi') {
/**
@@ -775,25 +747,7 @@ class AliasingState {
if (when >= index) {
continue;
}
queue.push({place: alias, transitive, direction: 'backwards', kind});
}
/**
* MaybeAlias indicates potential data flow from unknown function calls,
* so we downgrade mutations through these aliases to consider them
* conditional. This means we'll consider them for mutation *range*
* purposes but not report validation errors for mutations, since
* we aren't sure that the `from` value could actually be aliased.
*/
for (const [alias, when] of node.maybeAliases) {
if (when >= index) {
continue;
}
queue.push({
place: alias,
transitive,
direction: 'backwards',
kind: MutationKind.Conditional,
});
queue.push({place: alias, transitive, direction: 'backwards'});
}
}
/**
@@ -804,12 +758,7 @@ class AliasingState {
if (when >= index) {
continue;
}
queue.push({
place: capture,
transitive,
direction: 'backwards',
kind,
});
queue.push({place: capture, transitive, direction: 'backwards'});
}
}
}

View File

@@ -153,10 +153,6 @@ This is somewhat the inverse of `Capture`. The `CreateFrom` effect describes tha
Describes immutable data flow from one value to another. This is not currently used for anything, but is intended to eventually power a more sophisticated escape analysis.
### MaybeAlias
Describes potential data flow that the compiler knows may occur behind a function call, but cannot be sure about. For example, `foo(x)` _may_ be the identity function and return `x`, or `cond(a, b, c)` may conditionally return `b` or `c` depending on the value of `a`, but those functions could just as easily return new mutable values and not capture any information from their arguments. MaybeAlias represents that we have to consider the potential for data flow when deciding mutable ranges, but should be conservative about reporting errors. For example, `foo(someFrozenValue).property = true` should not error since we don't know for certain that foo returns its input.
### State-Changing Effects
The following effects describe state changes to specific values, not data flow. In many cases, JavaScript semantics will involve a combination of both data-flow effects *and* state-change effects. For example, `object.property = value` has data flow (`Capture object <- value`) and mutation (`Mutate object`).
@@ -351,17 +347,6 @@ a.b = b; // capture
mutate(a); // can transitively mutate b
```
### MaybeAlias makes mutation conditional
Because we don't know for certain that the aliasing occurs, we consider the mutation conditional against the source.
```
MaybeAlias a <- b
Mutate a
=>
MutateConditional b
```
### Freeze Does Not Freeze the Value
Freeze does not freeze the value itself:

View File

@@ -113,13 +113,8 @@ function* splitPragma(
*/
function parseConfigPragmaEnvironmentForTest(
pragma: string,
defaultConfig: PartialEnvironmentConfig,
): EnvironmentConfig {
// throw early if the defaults are invalid
EnvironmentConfigSchema.parse(defaultConfig);
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
defaultConfig;
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {};
for (const {key, value: val} of splitPragma(pragma)) {
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
@@ -179,13 +174,9 @@ export function parseConfigPragmaForTests(
pragma: string,
defaults: {
compilationMode: CompilationMode;
environment?: PartialEnvironmentConfig;
},
): PluginOptions {
const environment = parseConfigPragmaEnvironmentForTest(
pragma,
defaults.environment ?? {},
);
const environment = parseConfigPragmaEnvironmentForTest(pragma);
const options: Record<keyof PluginOptions, unknown> = {
...defaultOptions,
panicThreshold: 'all_errors',

View File

@@ -18,15 +18,15 @@ function Foo(props) {
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow
Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]`
$FlowFixMe[react-rule-hook].
error.bailout-on-flow-suppression.ts:4:2
2 |
3 | function Foo(props) {
> 4 | // $FlowFixMe[react-rule-hook]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
5 | useX();
6 | return null;
7 | }

View File

@@ -21,28 +21,28 @@ function lowercasecomponent() {
```
Found 2 errors:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable my-app/react-rule`
eslint-disable my-app/react-rule.
error.bailout-on-suppression-of-custom-rule.ts:3:0
1 | // @eslintSuppressionRules:["my-app","react-rule"]
2 |
> 3 | /* eslint-disable my-app/react-rule */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
4 | function lowercasecomponent() {
5 | 'use forget';
6 | const x = [];
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line my-app/react-rule`
eslint-disable-next-line my-app/react-rule.
error.bailout-on-suppression-of-custom-rule.ts:7:2
5 | 'use forget';
6 | const x = [];
> 7 | // eslint-disable-next-line my-app/react-rule
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
8 | return <div>{x}</div>;
9 | }
10 | /* eslint-enable my-app/react-rule */

View File

@@ -1,39 +0,0 @@
## Input
```javascript
import {useHook} from 'shared-runtime';
function Component(props) {
const frozen = useHook();
let x;
if (props.cond) {
x = frozen;
} else {
x = {};
}
x.property = true;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed.
error.invalid-mutate-phi-which-could-be-frozen.ts:11:2
9 | x = {};
10 | }
> 11 | x.property = true;
| ^ value cannot be modified
12 | }
13 |
```

View File

@@ -1,12 +0,0 @@
import {useHook} from 'shared-runtime';
function Component(props) {
const frozen = useHook();
let x;
if (props.cond) {
x = frozen;
} else {
x = {};
}
x.property = true;
}

View File

@@ -19,26 +19,26 @@ function lowercasecomponent() {
```
Found 2 errors:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`
eslint-disable react-hooks/rules-of-hooks.
error.invalid-sketchy-code-use-forget.ts:1:0
> 1 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
2 | function lowercasecomponent() {
3 | 'use forget';
4 | const x = [];
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks`
eslint-disable-next-line react-hooks/rules-of-hooks.
error.invalid-sketchy-code-use-forget.ts:5:2
3 | 'use forget';
4 | const x = [];
> 5 | // eslint-disable-next-line react-hooks/rules-of-hooks
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
6 | return <div>{x}</div>;
7 | }
8 | /* eslint-enable react-hooks/rules-of-hooks */

View File

@@ -38,14 +38,14 @@ function CrimesAgainstReact() {
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`
eslint-disable react-hooks/rules-of-hooks.
error.invalid-unclosed-eslint-suppression.ts:2:0
1 | // Note: Everything below this is sketchy
> 2 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
3 | function lowercasecomponent() {
4 | 'use forget';
5 | const x = [];

View File

@@ -22,15 +22,15 @@ function Component() {
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps`
eslint-disable-next-line react-hooks/exhaustive-deps.
error.sketchy-code-exhaustive-deps.ts:6:7
4 | () => {
5 | item.push(1);
> 6 | }, // eslint-disable-next-line react-hooks/exhaustive-deps
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
7 | []
8 | );
9 |

View File

@@ -23,13 +23,13 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks`
eslint-disable react-hooks/rules-of-hooks.
error.sketchy-code-rules-of-hooks.ts:1:0
> 1 | /* eslint-disable react-hooks/rules-of-hooks */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
2 | function lowercasecomponent() {
3 | const x = [];
4 | return <div>{x}</div>;

View File

@@ -1,61 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
function Component() {
const data = useIdentity(
new Map([
[0, 'value0'],
[1, 'value1'],
])
);
const items = [];
// NOTE: `i` is a context variable because it's reassigned and also referenced
// within a closure, the `onClick` handler of each item
// TODO: for loops create a unique environment on each iteration, which means
// that if the iteration variable is only updated in the updater, the variable
// is effectively const within the body and the "update" acts more like
// a re-initialization than a reassignment.
// Until we model this "new environment" semantic, we allow this case to error
for (let i = MIN; i <= MAX; i += INCREMENT) {
items.push(
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
);
}
return <>{items}</>;
}
const MIN = 0;
const MAX = 3;
const INCREMENT = 1;
export const FIXTURE_ENTRYPOINT = {
params: [],
fn: Component,
};
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX.
error.todo-for-loop-with-context-variable-iterator.ts:18:30
16 | // a re-initialization than a reassignment.
17 | // Until we model this "new environment" semantic, we allow this case to error
> 18 | for (let i = MIN; i <= MAX; i += INCREMENT) {
| ^ `i` cannot be modified
19 | items.push(
20 | <Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
21 | );
```

View File

@@ -0,0 +1,89 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
function Component() {
const data = useIdentity(
new Map([
[0, 'value0'],
[1, 'value1'],
])
);
const items = [];
// NOTE: `i` is a context variable because it's reassigned and also referenced
// within a closure, the `onClick` handler of each item
for (let i = MIN; i <= MAX; i += INCREMENT) {
items.push(
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
);
}
return <>{items}</>;
}
const MIN = 0;
const MAX = 3;
const INCREMENT = 1;
export const FIXTURE_ENTRYPOINT = {
params: [],
fn: Component,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
function Component() {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = new Map([
[0, "value0"],
[1, "value1"],
]);
$[0] = t0;
} else {
t0 = $[0];
}
const data = useIdentity(t0);
let t1;
if ($[1] !== data) {
const items = [];
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
items.push(
<Stringify
key={i}
onClick={() => data.get(i)}
shouldInvokeFns={true}
/>,
);
}
t1 = <>{items}</>;
$[1] = data;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
const MIN = 0;
const MAX = 3;
const INCREMENT = 1;
export const FIXTURE_ENTRYPOINT = {
params: [],
fn: Component,
};
```
### Eval output
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value0"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div>

View File

@@ -10,11 +10,6 @@ function Component() {
const items = [];
// NOTE: `i` is a context variable because it's reassigned and also referenced
// within a closure, the `onClick` handler of each item
// TODO: for loops create a unique environment on each iteration, which means
// that if the iteration variable is only updated in the updater, the variable
// is effectively const within the body and the "update" acts more like
// a re-initialization than a reassignment.
// Until we model this "new environment" semantic, we allow this case to error
for (let i = MIN; i <= MAX; i += INCREMENT) {
items.push(
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />

View File

@@ -16,7 +16,6 @@ function Component({foo}) {
```
Found 1 error:
Error: Cannot infer dependencies of this effect. This will break your build!
To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.

View File

@@ -23,7 +23,6 @@ function Component({foo}) {
```
Found 1 error:
Error: Cannot infer dependencies of this effect. This will break your build!
To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.

View File

@@ -1,77 +0,0 @@
## Input
```javascript
import {identity, makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const object = props.object;
const f = () => {
// The argument maybe-aliases into the return
const obj = identity(object);
obj.property = props.value;
return obj;
};
const obj = f();
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: makeObject_Primitives(), value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity, makeObject_Primitives, Stringify } from "shared-runtime";
function Example(props) {
const $ = _c(7);
const object = props.object;
let t0;
if ($[0] !== object || $[1] !== props.value) {
t0 = () => {
const obj = identity(object);
obj.property = props.value;
return obj;
};
$[0] = object;
$[1] = props.value;
$[2] = t0;
} else {
t0 = $[2];
}
const f = t0;
let t1;
if ($[3] !== f) {
t1 = f();
$[3] = f;
$[4] = t1;
} else {
t1 = $[4];
}
const obj_0 = t1;
let t2;
if ($[5] !== obj_0) {
t2 = <Stringify obj={obj_0} />;
$[5] = obj_0;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{ object: makeObject_Primitives(), value: 42 }],
};
```
### Eval output
(kind: ok) <div>{"obj":{"a":0,"b":"value1","c":true,"property":42}}</div>

View File

@@ -1,18 +0,0 @@
import {identity, makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const object = props.object;
const f = () => {
// The argument maybe-aliases into the return
const obj = identity(object);
obj.property = props.value;
return obj;
};
const obj = f();
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: makeObject_Primitives(), value: 42}],
};

View File

@@ -1,77 +0,0 @@
## Input
```javascript
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const object = props.object;
const f = () => {
// The receiver maybe-aliases into the return
const obj = object.makeObject();
obj.property = props.value;
return obj;
};
const obj = f();
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: {makeObject: makeObject_Primitives}, value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeObject_Primitives, Stringify } from "shared-runtime";
function Example(props) {
const $ = _c(7);
const object = props.object;
let t0;
if ($[0] !== object || $[1] !== props.value) {
t0 = () => {
const obj = object.makeObject();
obj.property = props.value;
return obj;
};
$[0] = object;
$[1] = props.value;
$[2] = t0;
} else {
t0 = $[2];
}
const f = t0;
let t1;
if ($[3] !== f) {
t1 = f();
$[3] = f;
$[4] = t1;
} else {
t1 = $[4];
}
const obj_0 = t1;
let t2;
if ($[5] !== obj_0) {
t2 = <Stringify obj={obj_0} />;
$[5] = obj_0;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{ object: { makeObject: makeObject_Primitives }, value: 42 }],
};
```
### Eval output
(kind: ok) <div>{"obj":{"a":0,"b":"value1","c":true,"property":42}}</div>

View File

@@ -1,18 +0,0 @@
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const object = props.object;
const f = () => {
// The receiver maybe-aliases into the return
const obj = object.makeObject();
obj.property = props.value;
return obj;
};
const obj = f();
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: {makeObject: makeObject_Primitives}, value: 42}],
};

View File

@@ -1,57 +0,0 @@
## Input
```javascript
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const obj = props.object.makeObject();
obj.property = props.value;
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: {makeObject: makeObject_Primitives}, value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeObject_Primitives, Stringify } from "shared-runtime";
function Example(props) {
const $ = _c(5);
let obj;
if ($[0] !== props.object || $[1] !== props.value) {
obj = props.object.makeObject();
obj.property = props.value;
$[0] = props.object;
$[1] = props.value;
$[2] = obj;
} else {
obj = $[2];
}
let t0;
if ($[3] !== obj) {
t0 = <Stringify obj={obj} />;
$[3] = obj;
$[4] = t0;
} else {
t0 = $[4];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{ object: { makeObject: makeObject_Primitives }, value: 42 }],
};
```
### Eval output
(kind: ok) <div>{"obj":{"a":0,"b":"value1","c":true,"property":42}}</div>

View File

@@ -1,12 +0,0 @@
import {makeObject_Primitives, Stringify} from 'shared-runtime';
function Example(props) {
const obj = props.object.makeObject();
obj.property = props.value;
return <Stringify obj={obj} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{object: {makeObject: makeObject_Primitives}, value: 42}],
};