diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index ee0d0f74c5..6bce5dca2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -10,48 +10,22 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; - -export enum ErrorSeverity { - /** - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some - * misunderstanding on the user’s part. - */ - InvalidJS = 'InvalidJS', - /** - * JS syntax that is not supported and which we do not plan to support. Developers should - * rewrite to use supported forms. - */ - UnsupportedJS = 'UnsupportedJS', - /** - * Code that breaks the rules of React. - */ - InvalidReact = 'InvalidReact', - /** - * Incorrect configuration of the compiler. - */ - InvalidConfig = 'InvalidConfig', - /** - * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve - * memoization. - */ - CannotPreserveMemoization = 'CannotPreserveMemoization', - /** - * Unhandled syntax that we don't support yet. - */ - Todo = 'Todo', - /** - * An unexpected internal error in the compiler that indicates critical issues that can panic - * the compiler. - */ - Invariant = 'Invariant', -} +import {ErrorSeverity} from './Utils/CompilerErrorSeverity'; +import { + ErrorCode, + ErrorCodeDetails, + LinterCategory, +} from './Utils/CompilerErrorCodes'; +export {ErrorSeverity}; +export {ErrorCode, ErrorCodeDetails, LinterCategory}; export type CompilerDiagnosticOptions = { severity: ErrorSeverity; category: string; - description: string; + description?: string | null | undefined; details: Array; suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; }; export type CompilerDiagnosticDetail = @@ -86,13 +60,28 @@ export type CompilerSuggestion = description: string; }; -export type CompilerErrorDetailOptions = { +export type PlainCompilerErrorDetailOptions = { + errorCode?: void; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; + severity: + | ErrorSeverity.Invariant + | ErrorSeverity.Todo + | ErrorSeverity.InvalidConfig; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; +export type CodedCompilerErrorDetailOptions = { + errorCode: ErrorCode; + description?: string | null | undefined; + loc: SourceLocation | null; + suggestions?: Array | null | undefined; + linterCategory?: LinterCategory | null | undefined; +}; + +export type CompilerErrorDetailOptions = + | PlainCompilerErrorDetailOptions + | CodedCompilerErrorDetailOptions; export type PrintErrorMessageOptions = { /** @@ -102,19 +91,72 @@ export type PrintErrorMessageOptions = { eslint: boolean; }; +export function makeCompilerDiagnostic( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + }, +): CompilerDiagnostic { + return makeCompilerDiagnostic(code, options); +} + export class CompilerDiagnostic { options: CompilerDiagnosticOptions; - constructor(options: CompilerDiagnosticOptions) { + /** + * Constructor is private to enforce that we either only create invariant diagnostics + * or use ErrorCodes + */ + private constructor(options: CompilerDiagnosticOptions) { this.options = options; } - static create( - options: Omit, - ): CompilerDiagnostic { + static create< + T extends CompilerDiagnosticOptions & {severity: ErrorSeverity.Invariant}, + >(options: Omit): CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } + static fromCode( + code: ErrorCode, + options?: { + description?: string; + suggestions?: Array | null | undefined; + details?: Array | null | undefined; + }, + ): CompilerDiagnostic { + const errorEntry = ErrorCodeDetails[code]; + let description = undefined; + if (errorEntry.description != null) { + description = errorEntry.description; + } + if (options?.description != null && options.description.length > 0) { + if (description != null && description.length > 0) { + description += ' '; + } else { + description = ''; + } + description += options.description; + } + + const diagnosticOptions: CompilerDiagnosticOptions = { + severity: errorEntry.severity, + category: errorEntry.reason, + description, + linterCategory: errorEntry.linterCategory, + suggestions: options?.suggestions, + details: options?.details ?? [], + }; + + return new CompilerDiagnostic(diagnosticOptions); + } + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return {options: {...this.options, linterCategory: undefined}}; + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -127,6 +169,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): CompilerDiagnosticOptions['linterCategory'] { + return this.options.linterCategory; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -138,11 +183,10 @@ export class CompilerDiagnostic { } printErrorMessage(source: string, options: PrintErrorMessageOptions): string { - const buffer = [ - printErrorSummary(this.severity, this.category), - '\n\n', - this.description, - ]; + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`\n\n${this.description}`); + } for (const detail of this.options.details) { switch (detail.kind) { case 'error': { @@ -202,13 +246,65 @@ export class CompilerErrorDetail { this.options = options; } - get reason(): CompilerErrorDetailOptions['reason'] { - return this.options.reason; + static fromCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + return new CompilerErrorDetail({ + ...details, + errorCode: code, + } as CodedCompilerErrorDetailOptions); } - get description(): CompilerErrorDetailOptions['description'] { + + // TODO: remove after converting test fixtures to use printErrorMessage + serialize(): unknown { + return { + options: { + reason: this.reason, + description: this.description, + severity: this.severity, + loc: this.loc, + suggestions: this.suggestions, + }, + }; + } + + get reason(): string { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].reason; + } else { + return this.options.reason; + } + } + get description(): string | null | undefined { + if (this.options.errorCode != null) { + let description = undefined; + if (ErrorCodeDetails[this.options.errorCode].description != null) { + description = ErrorCodeDetails[this.options.errorCode].description; + } + if ( + this.options.description != null && + this.options.description.length > 0 + ) { + if (description != null && description.length > 0) { + description += '. '; + } else { + description = ''; + } + description += this.options.description; + } + return description; + } return this.options.description; } - get severity(): CompilerErrorDetailOptions['severity'] { + get severity(): ErrorSeverity { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].severity; + } return this.options.severity; } get loc(): CompilerErrorDetailOptions['loc'] { @@ -217,6 +313,12 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get linterCategory(): LinterCategory | null | undefined { + if (this.options.errorCode != null) { + return ErrorCodeDetails[this.options.errorCode].linterCategory; + } + return undefined; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -266,7 +368,7 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); @@ -280,14 +382,14 @@ export class CompilerError extends Error { } } - static throwDiagnostic(options: CompilerDiagnosticOptions): never { + static throwDiagnostic(diagnostic: CompilerDiagnostic): never { const errors = new CompilerError(); - errors.pushDiagnostic(new CompilerDiagnostic(options)); + errors.pushDiagnostic(diagnostic); throw errors; } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -296,34 +398,21 @@ export class CompilerError extends Error { throw errors; } - static throwInvalidJS( - options: Omit, + static throwFromCode( + code: ErrorCode, + options?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, ): never { const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidJS, - }), - ); - throw errors; - } - - static throwInvalidReact( - options: Omit, - ): never { - const errors = new CompilerError(); - errors.pushErrorDetail( - new CompilerErrorDetail({ - ...options, - severity: ErrorSeverity.InvalidReact, - }), - ); + errors.pushErrorDetail(CompilerErrorDetail.fromCode(code, options)); throw errors; } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( @@ -392,13 +481,10 @@ export class CompilerError extends Error { } push(options: CompilerErrorDetailOptions): CompilerErrorDetail { - const detail = new CompilerErrorDetail({ - reason: options.reason, - description: options.description ?? null, - severity: options.severity, - suggestions: options.suggestions, - loc: typeof options.loc === 'symbol' ? null : options.loc, - }); + if (options instanceof CompilerErrorDetail) { + return this.pushErrorDetail(options); + } + const detail = new CompilerErrorDetail(options); return this.pushErrorDetail(detail); } @@ -407,6 +493,19 @@ export class CompilerError extends Error { return detail; } + pushErrorCode( + code: ErrorCode, + details?: { + description?: string | null; + loc?: SourceLocation | null; + suggestions?: Array | null | undefined; + }, + ): CompilerErrorDetail { + const detail = CompilerErrorDetail.fromCode(code, details); + this.details.push(detail); + return detail; + } + hasErrors(): boolean { return this.details.length > 0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 24ce37cf72..ea1d28a62e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -38,10 +38,10 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ - severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, loc: importDeclPath.node.loc ?? null, + severity: ErrorSeverity.Todo, }); } }, @@ -205,10 +205,10 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ - severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, loc: scope.getBinding(name)?.path.node.loc ?? null, + severity: ErrorSeverity.Todo, suggestions: null, }); return Err(error); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index c13940ed10..1a8e1457ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerErrorDetail, - CompilerErrorDetailOptions, + PlainCompilerErrorDetailOptions, } from '../CompilerError'; import { EnvironmentConfig, @@ -107,6 +107,8 @@ export type PluginOptions = { * passes. * * Defaults to false + * + * TODO: rename this to lintOnly or something similar */ noEmit: boolean; @@ -234,7 +236,10 @@ export type CompileErrorEvent = { export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; + detail: Omit< + Omit, + 'suggestions' + >; }; export type CompileSuccessEvent = { kind: 'CompileSuccess'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index a4f984f195..7a004fa18c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -100,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx'; import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; import {transformFire} from '../Transform'; import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender'; -import {CompilerError} from '..'; import {validateStaticComponents} from '../Validation/ValidateStaticComponents'; import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions'; import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects'; @@ -174,7 +173,7 @@ function runWithEnvironment( !env.config.disableMemoizationForDebugging && !env.config.enableChangeDetectionForDebugging ) { - dropManualMemoization(hir).unwrap(); + env.logOrThrowErrors(dropManualMemoization(hir)); log({kind: 'hir', name: 'DropManualMemoization', value: hir}); } @@ -207,10 +206,10 @@ function runWithEnvironment( if (env.isInferredMemoEnabled) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + env.logOrThrowErrors(validateHooksUsage(hir)); } - if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.config.validateNoCapitalizedCalls != null) { + env.logOrThrowErrors(validateNoCapitalizedCalls(hir)); } } @@ -230,20 +229,17 @@ function runWithEnvironment( log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); if (!env.config.enableNewMutationAliasingModel) { - const fnEffectErrors = inferReferenceEffects(hir); + const fnEffectResult = inferReferenceEffects(hir); + if (env.isInferredMemoEnabled) { - if (fnEffectErrors.length > 0) { - CompilerError.throw(fnEffectErrors[0]); - } + env.logOrThrowErrors(fnEffectResult); } log({kind: 'hir', name: 'InferReferenceEffects', value: hir}); } else { const mutabilityAliasingErrors = inferMutationAliasingEffects(hir); log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } + env.logOrThrowErrors(mutabilityAliasingErrors); } } @@ -272,10 +268,8 @@ function runWithEnvironment( }); log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir}); if (env.isInferredMemoEnabled) { - if (mutabilityAliasingErrors.isErr()) { - throw mutabilityAliasingErrors.unwrapErr(); - } - validateLocalsNotReassignedAfterRender(hir); + env.logOrThrowErrors(mutabilityAliasingErrors.map(() => undefined)); + env.logOrThrowErrors(validateLocalsNotReassignedAfterRender(hir)); } } @@ -285,15 +279,15 @@ function runWithEnvironment( } if (env.config.validateRefAccessDuringRender) { - validateNoRefAccessInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoRefAccessInRender(hir)); } if (env.config.validateNoSetStateInRender) { - validateNoSetStateInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoSetStateInRender(hir)); } if (env.config.validateNoDerivedComputationsInEffects) { - validateNoDerivedComputationsInEffects(hir); + env.logOrThrowErrors(validateNoDerivedComputationsInEffects(hir)); } if (env.config.validateNoSetStateInEffects) { @@ -305,14 +299,14 @@ function runWithEnvironment( } if (env.config.validateNoImpureFunctionsInRender) { - validateNoImpureFunctionsInRender(hir).unwrap(); + env.logOrThrowErrors(validateNoImpureFunctionsInRender(hir)); } if ( env.config.validateNoFreezingKnownMutableFunctions || env.config.enableNewMutationAliasingModel ) { - validateNoFreezingKnownMutableFunctions(hir).unwrap(); + env.logOrThrowErrors(validateNoFreezingKnownMutableFunctions(hir)); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 79bbee37a5..825f83a2a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -7,11 +7,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; @@ -32,6 +28,7 @@ import { } from './Suppression'; import {GeneratedSource} from '../HIR'; import {Err, Ok, Result} from '../Utils/Result'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; export type CompilerPass = { opts: PluginOptions; @@ -101,12 +98,9 @@ function findDirectivesDynamicGating( if (t.isValidIdentifier(maybeMatch[1])) { result.push({directive, match: maybeMatch[1]}); } else { - errors.push({ - reason: `Dynamic gating directive is not a valid JavaScript identifier`, + errors.pushErrorCode(ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, { description: `Found '${directive.value.value}'`, - severity: ErrorSeverity.InvalidReact, loc: directive.loc ?? null, - suggestions: null, }); } } @@ -115,14 +109,11 @@ function findDirectivesDynamicGating( return Err(errors); } else if (result.length > 1) { const error = new CompilerError(); - error.push({ - reason: `Multiple dynamic gating directives found`, + error.pushErrorCode(ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, { description: `Expected a single directive but found [${result .map(r => r.directive.value.value) .join(', ')}]`, - severity: ErrorSeverity.InvalidReact, loc: result[0].directive.loc ?? null, - suggestions: null, }); return Err(error); } else if (result.length === 1) { @@ -451,14 +442,12 @@ export function compileProgram( if (programContext.hasModuleScopeOptOut) { if (compiledFns.length > 0) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: - 'Unexpected compiled functions when module scope opt-out is present', - severity: ErrorSeverity.Invariant, - loc: null, - }), - ); + error.push({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }); handleError(error, programContext, null); } return null; @@ -591,9 +580,7 @@ function processFn( let compiledFn: CodegenFunction; const compileResult = tryCompileFunction(fn, fnType, programContext); if (compileResult.kind === 'error') { - if (directives.optOut != null) { - logError(compileResult.error, programContext, fn.node.loc ?? null); - } else { + if (directives.optOut == null) { handleError(compileResult.error, programContext, fn.node.loc ?? null); } const retryResult = retryCompileFunction(fn, fnType, programContext); @@ -692,7 +679,7 @@ function tryCompileFunction( fn, programContext.opts.environment, fnType, - 'all_features', + programContext.opts.noEmit ? 'lint_only' : 'all_features', programContext, programContext.opts.logger, programContext.filename, @@ -805,15 +792,7 @@ function shouldSkipCompilation( if (pass.opts.sources) { if (pass.filename === null) { const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); + error.pushErrorCode(ErrorCode.FILENAME_NOT_SET); handleError(error, pass, null); return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index ee341b111f..75828aa108 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,7 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, - ErrorSeverity, + ErrorCode, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; import {GeneratedSource} from '../HIR'; @@ -165,14 +165,12 @@ export function suppressionsToCompilerError( let reason, suggestion; switch (suppressionRange.source) { case 'Eslint': - reason = - 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled'; + reason = ErrorCode.BAILOUT_ESLINT_SUPPRESSION; suggestion = 'Remove the ESLint suppression and address the React error'; break; case 'Flow': - reason = - 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow'; + reason = ErrorCode.BAILOUT_FLOW_SUPPRESSION; suggestion = 'Remove the Flow suppression and address the React error'; break; default: @@ -182,10 +180,8 @@ export function suppressionsToCompilerError( ); } 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()}\``, - severity: ErrorSeverity.InvalidReact, + CompilerDiagnostic.fromCode(reason, { + description: `Found suppression \`${suppressionRange.disableComment.value.trim()}\``, suggestions: [ { description: suggestion, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 16d7c3713c..5271d66886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,27 +8,24 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; +import {CompilerError, EnvironmentConfig, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; -import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; -function throwInvalidReact( - options: Omit, +function logAndThrowDiagnostic( + diagnostic: CompilerDiagnostic, {logger, filename}: TraversalState, ): never { - const detail: CompilerDiagnosticOptions = { - severity: ErrorSeverity.InvalidReact, - ...options, - }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail: new CompilerDiagnostic(detail), + detail: diagnostic, }); - CompilerError.throwDiagnostic(detail); + CompilerError.throwDiagnostic(diagnostic); } function isAutodepsSigil( @@ -90,10 +87,8 @@ function assertValidEffectImportReference( * as it may have already been transformed by the compiler (and not * memoized). */ - throwInvalidReact( - { - category: - 'Cannot infer dependencies of this effect. This will break your build!', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.DID_NOT_INFER_DEPS, { description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), @@ -104,7 +99,7 @@ function assertValidEffectImportReference( loc: parent.node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } @@ -121,10 +116,8 @@ function assertValidFireImportReference( paths[0], context.transformErrors, ); - throwInvalidReact( - { - category: - '[Fire] Untransformed reference to compiler-required feature.', + logAndThrowDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.CANNOT_COMPILE_FIRE, { description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic @@ -137,7 +130,7 @@ function assertValidFireImportReference( loc: paths[0].node.loc ?? GeneratedSource, }, ], - }, + }), context, ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts index fa3f551ff5..85d6bd0457 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts @@ -1,4 +1,5 @@ import {CompilerError, SourceLocation} from '..'; +import {ErrorCode} from '../CompilerError'; import { ConcreteType, printConcrete, @@ -12,8 +13,8 @@ export function unsupportedLanguageFeature( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support language feature: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support language feature: ${desc}`, loc, }); } @@ -49,16 +50,16 @@ export function raiseUnificationErrors( loc, }); } else if (errs.length === 1) { - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because ${printUnificationError(errs[0])}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because ${printUnificationError(errs[0])}`, loc, }); } else { const messages = errs .map(err => `\t* ${printUnificationError(err)}`) .join('\n'); - CompilerError.throwInvalidJS({ - reason: `Unable to unify types because:\n${messages}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to unify types because:\n${messages}`, loc, }); } @@ -69,21 +70,21 @@ export function unresolvableTypeVariable( id: VariableId, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Unable to resolve free variable ${id} to a concrete type`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Unable to resolve free variable ${id} to a concrete type`, loc, }); } export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never { if (explicit) { - CompilerError.throwInvalidJS({ - reason: `Undefined is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Undefined is not a valid operand of \`+\``, loc, }); } else { - CompilerError.throwInvalidJS({ - reason: `Value may be undefined, which is not a valid operand of \`+\``, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Value may be undefined, which is not a valid operand of \`+\``, loc, }); } @@ -93,8 +94,8 @@ export function unsupportedTypeAnnotation( desc: string, loc: SourceLocation, ): never { - CompilerError.throwInvalidJS({ - reason: `Typedchecker does not currently support type annotation: ${desc}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Typedchecker does not currently support type annotation: ${desc}`, loc, }); } @@ -106,16 +107,16 @@ export function checkTypeArgumentArity( loc: SourceLocation, ): void { if (expected !== actual) { - CompilerError.throwInvalidJS({ - reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Expected ${desc} to have ${expected} type parameters, got ${actual}`, loc, }); } } export function notAFunction(desc: string, loc: SourceLocation): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} because it is not a function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} because it is not a function`, loc, }); } @@ -124,8 +125,8 @@ export function notAPolymorphicFunction( desc: string, loc: SourceLocation, ): void { - CompilerError.throwInvalidJS({ - reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, + CompilerError.throwFromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { + description: `Cannot call ${desc} with type arguments because it is not a polymorphic function`, loc, }); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 3b11670146..26f05f58ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCode, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -170,14 +171,12 @@ export function lower( ); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + CompilerDiagnostic.fromCode(ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, { description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', loc: param.node.loc ?? null, - message: 'Unsupported parameter type', + message: `Unsupported parameter type: ${param.node.type}`, }), ); } @@ -201,14 +200,12 @@ export function lower( directives = body.get('directives').map(d => d.node.value.value); } else { builder.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + CompilerDiagnostic.fromCode(ErrorCode.INVALID_JAVASCRIPT_AST, { description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', loc: body.node.loc ?? null, - message: 'Expected a block statement or expression', + message: 'Change this to a block statement or expression', }), ); } @@ -769,12 +766,12 @@ function lowerStatement( const testExpr = case_.get('test'); if (testExpr.node == null) { if (hasDefault) { - builder.errors.push({ - reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, - severity: ErrorSeverity.InvalidJS, - loc: case_.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + { + loc: case_.node.loc ?? null, + }, + ); break; } hasDefault = true; @@ -886,19 +883,20 @@ function lowerStatement( if (builder.isContextIdentifier(id)) { if (kind === InstructionKind.Const) { const declRangeStart = declaration.parentPath.node.start!; - builder.errors.push({ - reason: `Expect \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: id.node.loc ?? null, - suggestions: [ - { - description: 'Change to a `let` declaration', - op: CompilerSuggestionOperation.Replace, - range: [declRangeStart, declRangeStart + 5], // "const".length - text: 'let', - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: id.node.loc ?? null, + suggestions: [ + { + description: 'Change to a `let` declaration', + op: CompilerSuggestionOperation.Replace, + range: [declRangeStart, declRangeStart + 5], // "const".length + text: 'let', + }, + ], + }, + ); } lowerValueToTemporary(builder, { kind: 'DeclareContext', @@ -932,13 +930,13 @@ function lowerStatement( } } } else { - builder.errors.push({ - reason: `Expected variable declaration to be an identifier if no initializer was provided`, - description: `Got a \`${id.type}\``, - severity: ErrorSeverity.InvalidJS, - loc: stmt.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + { + description: `Got a \`${id.type}\``, + loc: stmt.node.loc ?? null, + }, + ); } } return; @@ -1374,12 +1372,8 @@ function lowerStatement( return; } case 'WithStatement': { - builder.errors.push({ - reason: `JavaScript 'with' syntax is not supported`, - description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_WITH, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1394,12 +1388,8 @@ function lowerStatement( * and complex enough to support that we don't anticipate supporting anytime soon. Developers * are encouraged to lift classes out of component/hook declarations. */ - builder.errors.push({ - reason: 'Inline `class` declarations are not supported', - description: `Move class declarations outside of components/hooks`, - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_INNER_CLASS, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1423,12 +1413,8 @@ function lowerStatement( case 'ImportDeclaration': case 'TSExportAssignment': case 'TSImportEqualsDeclaration': { - builder.errors.push({ - reason: - 'JavaScript `import` and `export` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_IMPORT_EXPORT, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1438,12 +1424,8 @@ function lowerStatement( return; } case 'TSNamespaceExportDeclaration': { - builder.errors.push({ - reason: - 'TypeScript `namespace` statements may only appear at the top level of a module', - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_TS_NAMESPACE, { loc: stmtPath.node.loc ?? null, - suggestions: null, }); lowerValueToTemporary(builder, { kind: 'UnsupportedNode', @@ -1698,12 +1680,8 @@ function lowerExpression( const expr = exprPath as NodePath; const calleePath = expr.get('callee'); if (!calleePath.isExpression()) { - builder.errors.push({ - reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, - description: `Got a \`${calleePath.node.type}\``, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_NEW_EXPRESSION, { loc: calleePath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -1800,12 +1778,12 @@ function lowerExpression( last = lowerExpressionToTemporary(builder, item); } if (last === null) { - builder.errors.push({ - reason: `Expected sequence expression to have at least one expression`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + { + loc: expr.node.loc ?? null, + }, + ); } else { lowerValueToTemporary(builder, { kind: 'StoreLocal', @@ -2289,18 +2267,18 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', - description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, - details: locations.map(loc => { - return { - kind: 'error', - message: `Multiple \`<${tagName}:${name}>\` tags found`, - loc, - }; + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_DUPLICATE_FBT_TAGS, { + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }), - }); + ); } } } @@ -2389,11 +2367,8 @@ function lowerExpression( const quasis = expr.get('quasis'); if (subexprs.length !== quasis.length - 1) { - builder.errors.push({ - reason: `Unexpected quasi and subexpression lengths in template literal`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_QUASI_LENGTHS, { loc: exprPath.node.loc ?? null, - suggestions: null, }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } @@ -2441,24 +2416,23 @@ function lowerExpression( }; } } else { - builder.errors.push({ - reason: `Only object properties can be deleted`, - severity: ErrorSeverity.InvalidJS, - loc: expr.node.loc ?? null, - suggestions: [ - { - description: 'Remove this line', - range: [expr.node.start!, expr.node.end!], - op: CompilerSuggestionOperation.Remove, - }, - ], - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + { + loc: expr.node.loc ?? null, + suggestions: [ + { + description: 'Remove this line', + range: [expr.node.start!, expr.node.end!], + op: CompilerSuggestionOperation.Remove, + }, + ], + }, + ); return {kind: 'UnsupportedNode', node: expr.node, loc: exprLoc}; } } else if (expr.node.operator === 'throw') { - builder.errors.push({ - reason: `Throw expressions are not supported`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_THROW_EXPRESSION, { loc: expr.node.loc ?? null, suggestions: [ { @@ -3285,10 +3259,8 @@ function lowerJsxElementName( const name = exprPath.node.name.name; const tag = `${namespace}:${name}`; if (namespace.indexOf(':') !== -1 || name.indexOf(':') !== -1) { - builder.errors.push({ - reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + builder.errors.pushErrorCode(ErrorCode.INVALID_JSX_NAMESPACED_NAME, { description: `Got \`${namespace}\` : \`${name}\``, - severity: ErrorSeverity.InvalidJS, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3583,11 +3555,7 @@ function lowerIdentifier( } default: { if (binding.kind === 'Global' && binding.name === 'eval') { - builder.errors.push({ - reason: `The 'eval' function is not supported`, - description: - 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', - severity: ErrorSeverity.UnsupportedJS, + builder.errors.pushErrorCode(ErrorCode.UNSUPPORTED_EVAL, { loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3653,9 +3621,7 @@ function lowerIdentifierForAssignment( binding.bindingKind === 'const' && kind === InstructionKind.Reassign ) { - builder.errors.push({ - reason: `Cannot reassign a \`const\` variable`, - severity: ErrorSeverity.InvalidJS, + builder.errors.pushErrorCode(ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, { loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3710,12 +3676,13 @@ function lowerAssignment( let temporary; if (builder.isContextIdentifier(lvalue)) { if (kind === InstructionKind.Const && !isHoistedIdentifier) { - builder.errors.push({ - reason: `Expected \`const\` declaration not to be reassigned`, - severity: ErrorSeverity.InvalidJS, - loc: lvalue.node.loc ?? null, - suggestions: null, - }); + builder.errors.pushErrorCode( + ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + { + loc: lvalue.node.loc ?? null, + suggestions: null, + }, + ); } if ( @@ -3726,7 +3693,8 @@ function lowerAssignment( ) { builder.errors.push({ reason: `Unexpected context variable kind`, - severity: ErrorSeverity.InvalidJS, + description: `Expected one of Const, Reassign, Let, Function, got ${kind}`, + severity: ErrorSeverity.Invariant, loc: lvalue.node.loc ?? null, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 957c5ab84a..ad1815a62d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -93,7 +93,7 @@ export const MacroSchema = z.union([ z.tuple([z.string(), z.array(MacroMethodSchema)]), ]); -export type CompilerMode = 'all_features' | 'no_inferred_memo'; +export type CompilerMode = 'all_features' | 'no_inferred_memo' | 'lint_only'; export type Macro = z.infer; export type MacroMethod = z.infer; @@ -829,6 +829,14 @@ export class Environment { } } + logOrThrowErrors(errors: Result): void { + if (this.compilerMode === 'lint_only') { + this.logErrors(errors); + } else { + errors.unwrap(); + } + } + isContextIdentifier(node: t.Identifier): boolean { return this.#contextIdentifiers.has(node); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index 51715b3c1e..89b2c0fc41 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -15,6 +15,7 @@ import {Type, makeType} from './Types'; import {z} from 'zod'; import type {AliasingEffect} from '../Inference/AliasingEffects'; import {isReservedWord} from '../Utils/Keyword'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -1322,18 +1323,18 @@ export function forkTemporaryIdentifier( */ export function makeIdentifierName(name: string): ValidatedIdentifier { if (isReservedWord(name)) { - CompilerError.throwInvalidJS({ - reason: 'Expected a non-reserved identifier name', - loc: GeneratedSource, - description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, - suggestions: null, - }); + CompilerError.throwFromCode( + ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + { + loc: GeneratedSource, + description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`, + }, + ); } else { CompilerError.invariant(t.isValidIdentifier(name), { reason: `Expected a valid identifier name`, loc: GeneratedSource, description: `\`${name}\` is not a valid JavaScript identifier`, - suggestions: null, }); } return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 81959ea361..1d71452d0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -37,6 +37,7 @@ import { mapTerminalSuccessors, terminalFallthrough, } from './visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * ******************************************************************************************* @@ -308,19 +309,17 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwDiagnostic({ - severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', - description: - 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', - details: [ - { - kind: 'error', - message: 'Rename to avoid conflict with fbt plugin', - loc: node.loc ?? GeneratedSource, - }, - ], - }); + CompilerError.throwDiagnostic( + CompilerDiagnostic.fromCode(ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, { + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], + }), + ); } const originalName = node.name; let name = originalName; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 7aeb3edb22..fa9bddd3f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -5,12 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, - SourceLocation, -} from '..'; +import {CompilerDiagnostic, CompilerError, SourceLocation} from '..'; import { CallExpression, Effect, @@ -35,6 +30,7 @@ import { makeInstructionId, } from '../HIR'; import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; type ManualMemoCallee = { @@ -299,12 +295,11 @@ function extractManualMemoizationArgs( >; if (fnPlace == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, - description: `Expected a callback function to be passed to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + kind === 'useMemo' + ? ErrorCode.INVALID_USE_MEMO_NO_ARG0 + : ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Expected a callback function to be passed to ${kind}`, @@ -314,12 +309,11 @@ function extractManualMemoizationArgs( } if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, - description: `Unexpected spread argument to ${kind}`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + fnPlace.kind === 'Spread' + ? ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT + : ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: `Unexpected spread argument to ${kind}`, @@ -334,12 +328,9 @@ function extractManualMemoizationArgs( ); if (maybeDepsList == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, - description: `Expected the dependency list for ${kind} to be an array literal`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + ).withDetail({ kind: 'error', loc: depsListPlace.loc, message: `Expected the dependency list for ${kind} to be an array literal`, @@ -352,12 +343,9 @@ function extractManualMemoizationArgs( const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); if (maybeDep == null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + ).withDetail({ kind: 'error', loc: dep.loc, message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, @@ -457,16 +445,16 @@ export function dropManualMemoization( if (funcToCheck !== undefined && funcToCheck.loweredFunc.func) { if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', - description: `This ${ - manualMemo.loadInstr.value.kind === 'PropertyLoad' - ? 'React.useMemo' - : 'useMemo' - } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + { + description: `This ${ + manualMemo.loadInstr.value.kind === 'PropertyLoad' + ? 'React.useMemo' + : 'useMemo' + } callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`, + }, + ).withDetail({ kind: 'error', loc: instr.value.loc, message: 'useMemo() callbacks must return a value', @@ -497,12 +485,9 @@ export function dropManualMemoization( */ if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, - description: `Expected the first argument to be an inline function expression`, - suggestions: [], - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + ).withDetail({ kind: 'error', loc: fnPlace.loc, message: `Expected the first argument to be an inline function expression`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index a01ca188a0..5fcf8a3cae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -25,6 +25,7 @@ import { isRefOrRefValue, } from '../HIR'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {assertExhaustive} from '../Utils/utils'; interface State { @@ -62,22 +63,20 @@ function inferOperandEffect(state: State, place: Place): null | FunctionEffect { // We ignore mutations of primitives since this is not a React-specific problem value.kind !== ValueKind.Primitive ) { - let reason = getWriteErrorReason(value); + let errorCode = getWriteErrorReason(value); return { kind: value.reason.size === 1 && value.reason.has(ValueReason.Global) ? 'GlobalMutation' : 'ReactMutation', error: { - reason, + errorCode, description: place.identifier.name !== null && place.identifier.name.kind === 'named' ? `Found mutation of \`${place.identifier.name.value}\`` : null, loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }; } @@ -266,11 +265,8 @@ export function inferInstructionFunctionEffects( functionEffects.push({ kind: 'GlobalMutation', error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + errorCode: ErrorCode.INVALID_WRITE_GLOBAL, loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, }, }); break; @@ -324,28 +320,28 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { return effect.kind === 'GlobalMutation'; } -export function getWriteErrorReason(abstractValue: AbstractValue): string { +export function getWriteErrorReason(abstractValue: AbstractValue): ErrorCode { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; + return ErrorCode.INVALID_WRITE_GLOBAL; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; + return ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Modifying a value returned from 'useContext()' is not allowed.`; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Modifying a value returned from a function whose return value should not be mutated'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; + return ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; + return ErrorCode.INVALID_WRITE_STATE; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; + return ErrorCode.INVALID_WRITE_REDUCER_STATE; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; + return ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; + return ErrorCode.INVALID_WRITE_HOOK_CAPTURED; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; + return ErrorCode.INVALID_WRITE_HOOK_RETURN; } else { - return 'This modifies a variable that React considers immutable'; + return ErrorCode.INVALID_WRITE_GENERIC; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index 2adf78fe05..60146d8335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -9,7 +9,6 @@ import { CompilerDiagnostic, CompilerError, Effect, - ErrorSeverity, SourceLocation, ValueKind, } from '..'; @@ -69,6 +68,7 @@ import {getWriteErrorReason} from './InferFunctionEffects'; import prettyFormat from 'pretty-format'; import {createTemporaryPlace} from '../HIR/HIRBuilder'; import {AliasingEffect, AliasingSignature, hashEffect} from './AliasingEffects'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; const DEBUG = false; @@ -442,7 +442,7 @@ function applySignature( const value = state.kind(effect.value); switch (value.kind) { case ValueKind.Frozen: { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -455,10 +455,9 @@ function applySignature( effects.push({ kind: 'MutateFrozen', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -1026,22 +1025,20 @@ function applyEffect( const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); - const diagnostic = CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', - description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`, - }); + const diagnostic = CompilerDiagnostic.fromCode( + ErrorCode.INVALID_ACCESS_BEFORE_INIT, + ); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { diagnostic.withDetail({ kind: 'error', loc: hoistedAccess.loc, - message: `${variable ?? 'variable'} accessed before it is declared`, + message: `${variable ?? 'This variable'} is accessed before it is declared`, }); } diagnostic.withDetail({ kind: 'error', loc: effect.value.loc, - message: `${variable ?? 'variable'} is declared here`, + message: `${variable ?? 'This variable'} is declared here`, }); applyEffect( @@ -1056,7 +1053,7 @@ function applyEffect( effects, ); } else { - const reason = getWriteErrorReason({ + const errorCode = getWriteErrorReason({ kind: value.kind, reason: value.reason, context: new Set(), @@ -1075,10 +1072,9 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', - description: `${reason}.`, + // TODO: remove ERROR_CODE.INVALID_WRITE and update test fixtures + error: CompilerDiagnostic.fromCode(ErrorCode.INVALID_WRITE, { + description: ErrorCodeDetails[errorCode].reason + '.', }).withDetail({ kind: 'error', loc: effect.value.loc, @@ -2006,15 +2002,12 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'Cannot reassign variables declared outside of the component/hook', - description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`, - }).withDetail({ + error: CompilerDiagnostic.fromCode( + ErrorCode.INVALID_WRITE_GLOBAL, + ).withDetail({ kind: 'error', loc: instr.loc, - message: `${variable} cannot be reassigned`, + message: `${variable} should not be reassigned`, }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); @@ -2105,19 +2098,16 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - }).withDetail({ - kind: 'error', - loc, - message: 'Cannot call impure function', - }), + error: CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail( + { + kind: 'error', + loc, + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', + }, + ), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 1b0856791a..a806ebd4bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -48,6 +48,7 @@ import { eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; +import {Err, Ok, Result} from '../Utils/Result'; import {assertExhaustive, Set_isSuperset} from '../Utils/utils'; import { inferTerminalFunctionEffects, @@ -106,7 +107,7 @@ const UndefinedValue: InstructionValue = { export default function inferReferenceEffects( fn: HIRFunction, options: {isFunctionExpression: boolean} = {isFunctionExpression: false}, -): Array { +): Result { /* * Initial state contains function params * TODO: include module declarations here as well @@ -247,10 +248,17 @@ export default function inferReferenceEffects( if (options.isFunctionExpression) { fn.effects = functionEffects; - return []; } else { - return transformFunctionEffectErrors(functionEffects); + const errors = transformFunctionEffectErrors(functionEffects); + if (errors.length > 0) { + const compilerError = new CompilerError(); + for (const detail of errors) { + compilerError.push(detail); + } + return Err(compilerError); + } } + return Ok(void 0); } type FreezeAction = {values: Set; reason: Set}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index f88c85f2f0..dbb84944b3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -42,6 +42,7 @@ import { import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; /* * TODO(jmbrown): @@ -50,8 +51,6 @@ import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; * - React.useEffect calls */ -const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`'; - export function transformFire(fn: HIRFunction): void { const context = new Context(fn.env); replaceFireFunctions(fn, context); @@ -178,9 +177,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } else if (value.args.length > 1 && value.args[1].kind === 'Spread') { @@ -188,9 +185,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.args[1].place.loc, description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -243,11 +238,9 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } else { context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description: '`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed', - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else { @@ -262,10 +255,8 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { } context.pushError({ loc: value.loc, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, description, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, }); } } else if (value.kind === 'CallExpression') { @@ -394,9 +385,7 @@ function ensureNoRemainingCalleeCaptures( description: `All uses of ${calleeName} must be either used with a fire() call in \ this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \ ${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`, - severity: ErrorSeverity.InvalidReact, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } @@ -411,9 +400,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', - severity: ErrorSeverity.Invariant, - reason: CANNOT_COMPILE_FIRE, - suggestions: null, + errorCode: ErrorCode.CANNOT_COMPILE_FIRE, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts new file mode 100644 index 0000000000..2fd3244fc5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorCodes.ts @@ -0,0 +1,611 @@ +import {ErrorSeverity} from './CompilerErrorSeverity'; + +export enum LinterCategory { + RULES_OF_HOOKS = 'rules-of-hooks', + IMPURE_FUNCTIONS = 'impure-functions', + JSX_IN_TRY = 'jsx-in-try', + NO_REF_ACCESS_IN_RENDER = 'no-ref-access-in-render', + VALIDATE_MANUAL_MEMO = 'validate-manual-memo', + DYNAMIC_MANUAL_MEMO = 'dynamic-manual-memo', + + EXHAUSTIVE_DEPS = 'exhaustive-deps', + MEMOIZED_DEPENDENCIES = 'memoized-dependencies', // not real! + INVALID_WRITE = 'invalid-write', + CAPITALIZED_CALLS = 'capitalized-calls', + STATIC_COMPONENTS = 'static-components', + NO_SET_STATE_IN_RENDER = 'no-set-state-in-render', + NO_SET_STATE_IN_EFFECTS = 'no-set-state-in-effects', + UNNECESSARY_EFFECTS = 'unnecessary-effects', + + UNSUPPORTED_SYNTAX = 'unsupported-syntax', + + TODO_SYNTAX = 'todo-syntax', + + COMPILER_CONFIG = 'compiler-config', +} + +export enum ErrorCode { + HOOK_CALL_STATIC, + HOOK_INVALID_REFERENCE, + HOOK_CALL_REACTIVE, + HOOK_CALL_NOT_TOP_LEVEL, + IMPURE_FUNCTIONS, + JSX_IN_TRY, + NO_REF_ACCESS_IN_RENDER, + WRITE_AFTER_RENDER, + REASSIGN_IN_ASYNC, + INVALID_WRITE, + CAPITALIZED_CALLS, + STATIC_COMPONENTS, + INVALID_USE_MEMO_CALLBACK_RETURN, + INVALID_USE_MEMO_CALLBACK_PARAMETERS, + INVALID_USE_MEMO_CALLBACK_ASYNC, + INVALID_USE_MEMO_NO_ARG0, + INVALID_USE_CALLBACK_NO_ARG0, + DYNAMIC_MANUAL_MEMO_CALLBACK, + DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + + INVALID_SET_STATE_IN_RENDER, + INVALID_SET_STATE_IN_MEMO, + INVALID_SET_STATE_IN_EFFECTS, + + NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + + DYNAMIC_GATING_IS_NOT_IDENTIFIER, + DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + FILENAME_NOT_SET, + + INVALID_WRITE_GLOBAL, + INVALID_WRITE_FROZEN_VALUE_JSX, + INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + INVALID_WRITE_IMMUTABLE_ARGS, + INVALID_WRITE_STATE, + INVALID_WRITE_REDUCER_STATE, + INVALID_WRITE_EFFECT_DEPENDENCY, + INVALID_WRITE_HOOK_CAPTURED, + INVALID_WRITE_HOOK_RETURN, + INVALID_ACCESS_BEFORE_INIT, + INVALID_WRITE_GENERIC, + + MEMOIZED_EFFECT_DEPENDENCIES, + + INVALID_SYNTAX_MULTIPLE_DEFAULTS, + INVALID_SYNTAX_REASSIGNED_CONST, + INVALID_SYNTAX_BAD_VARIABLE_DECL, + UNSUPPORTED_WITH, + UNSUPPORTED_INNER_CLASS, + INVALID_IMPORT_EXPORT, + INVALID_TS_NAMESPACE, + UNSUPPORTED_NEW_EXPRESSION, + UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + INVALID_QUASI_LENGTHS, + INVALID_SYNTAX_DELETE_EXPRESSION, + UNSUPPORTED_THROW_EXPRESSION, + INVALID_JSX_NAMESPACED_NAME, + UNSUPPORTED_EVAL, + INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + INVALID_JAVASCRIPT_AST, + BAILOUT_ESLINT_SUPPRESSION, + BAILOUT_FLOW_SUPPRESSION, + MANUAL_MEMO_MUTATED_LATER, + MANUAL_MEMO_REMOVED, + MANUAL_MEMO_DEPENDENCIES_CONFLICT, + + CANNOT_COMPILE_FIRE, + DID_NOT_INFER_DEPS, + + /** Todo syntax */ + TODO_CONFLICTING_FBT_IDENTIFIER, + TODO_DUPLICATE_FBT_TAGS, + UNKNOWN_FUNCTION_PARAMETERS, +} + +type ErrorCodeType = { + code: ErrorCode; + description?: string; + severity: ErrorSeverity; + reason: string; + linterCategory: LinterCategory | null; +}; + +export const ErrorCodeDetails: Record = { + [ErrorCode.HOOK_CALL_STATIC]: { + code: ErrorCode.HOOK_CALL_STATIC, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_INVALID_REFERENCE]: { + code: ErrorCode.HOOK_INVALID_REFERENCE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_REACTIVE]: { + code: ErrorCode.HOOK_CALL_REACTIVE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.HOOK_CALL_NOT_TOP_LEVEL]: { + code: ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, + severity: ErrorSeverity.InvalidReact, + reason: + 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', + linterCategory: LinterCategory.RULES_OF_HOOKS, + }, + [ErrorCode.IMPURE_FUNCTIONS]: { + code: ErrorCode.IMPURE_FUNCTIONS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot call impure functions during render', + description: + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).', + linterCategory: LinterCategory.IMPURE_FUNCTIONS, + }, + [ErrorCode.JSX_IN_TRY]: { + code: ErrorCode.JSX_IN_TRY, + severity: ErrorSeverity.InvalidReact, + reason: 'Avoid constructing JSX within try/catch', + description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + linterCategory: LinterCategory.JSX_IN_TRY, + }, + [ErrorCode.NO_REF_ACCESS_IN_RENDER]: { + code: ErrorCode.NO_REF_ACCESS_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access refs during render', + description: + 'React refs are values that are not needed for rendering. Refs should only be accessed ' + + 'outside of render, such as in event handlers or effects. ' + + 'Accessing a ref value (the `current` property) during render can cause your component ' + + 'not to update as expected (https://react.dev/reference/react/useRef)', + linterCategory: LinterCategory.NO_REF_ACCESS_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_EFFECTS]: { + code: ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Calling setState synchronously within an effect can trigger cascading renders', + description: + 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + + 'In general, the body of an effect should do one or both of the following:\n' + + '* Update external systems with the latest state from React.\n' + + '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + + 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + + '(https://react.dev/learn/you-might-not-need-an-effect)', + linterCategory: LinterCategory.NO_SET_STATE_IN_EFFECTS, + }, + [ErrorCode.WRITE_AFTER_RENDER]: { + code: ErrorCode.WRITE_AFTER_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot modify local variables after render completes', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.REASSIGN_IN_ASYNC]: { + code: ErrorCode.REASSIGN_IN_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variable in async function', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE]: { + code: ErrorCode.INVALID_WRITE, + severity: ErrorSeverity.InvalidReact, + reason: 'This value cannot be modified', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.CAPITALIZED_CALLS]: { + code: ErrorCode.CAPITALIZED_CALLS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config', + linterCategory: LinterCategory.CAPITALIZED_CALLS, + }, + [ErrorCode.STATIC_COMPONENTS]: { + code: ErrorCode.STATIC_COMPONENTS, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot create components during render', + linterCategory: LinterCategory.STATIC_COMPONENTS, + }, + [ErrorCode.INVALID_USE_MEMO_NO_ARG0]: { + code: ErrorCode.INVALID_USE_MEMO_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useMemo`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_CALLBACK_NO_ARG0]: { + code: ErrorCode.INVALID_USE_CALLBACK_NO_ARG0, + severity: ErrorSeverity.InvalidReact, + reason: `Expected a callback function to be passed to useCallback`, + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_MEMO_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useMemo', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + + [ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT]: { + code: ErrorCode.DYNAMIC_USE_CALLBACK_SPREAD_ARGUMENT, + severity: ErrorSeverity.InvalidReact, + reason: 'Unexpected spread argument to useCallback', + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not accept parameters', + description: + 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks may not be async or generator functions', + description: + 'useMemo() callbacks are called once and must synchronously return a value.', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN]: { + code: ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: 'useMemo() callbacks must return a value', + linterCategory: LinterCategory.VALIDATE_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_CALLBACK, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the first argument to be an inline function expression`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST]: { + code: ErrorCode.DYNAMIC_MANUAL_MEMO_DEPENDENCY_LIST, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list of useMemo or useCallback to be an array literal`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY]: { + code: ErrorCode.COMPLEX_MANUAL_MEMO_DEPENDENCY_LIST_ENTRY, + severity: ErrorSeverity.InvalidReact, + reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + linterCategory: LinterCategory.DYNAMIC_MANUAL_MEMO, + }, + [ErrorCode.INVALID_SET_STATE_IN_RENDER]: { + code: ErrorCode.INVALID_SET_STATE_IN_RENDER, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState during render may trigger an infinite loop', + description: + 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.INVALID_SET_STATE_IN_MEMO]: { + code: ErrorCode.INVALID_SET_STATE_IN_MEMO, + severity: ErrorSeverity.InvalidReact, + reason: 'Calling setState from useMemo may trigger an infinite loop', + description: + 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', + linterCategory: LinterCategory.NO_SET_STATE_IN_RENDER, + }, + [ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS]: { + code: ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', + linterCategory: LinterCategory.UNNECESSARY_EFFECTS, + }, + + /** Invalid writes */ + [ErrorCode.INVALID_WRITE_GLOBAL]: { + code: ErrorCode.INVALID_WRITE_GLOBAL, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX]: { + code: ErrorCode.INVALID_WRITE_FROZEN_VALUE_JSX, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_USE_CONTEXT, + severity: ErrorSeverity.InvalidReact, + reason: `Modifying a value returned from 'useContext()' is not allowed.`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_VALUE_KNOWN_SIGNATURE, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a function whose return value should not be mutated', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS]: { + code: ErrorCode.INVALID_WRITE_IMMUTABLE_ARGS, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_STATE]: { + code: ErrorCode.INVALID_WRITE_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_REDUCER_STATE]: { + code: ErrorCode.INVALID_WRITE_REDUCER_STATE, + severity: ErrorSeverity.InvalidReact, + reason: + "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead", + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY]: { + code: ErrorCode.INVALID_WRITE_EFFECT_DEPENDENCY, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_CAPTURED]: { + code: ErrorCode.INVALID_WRITE_HOOK_CAPTURED, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_HOOK_RETURN]: { + code: ErrorCode.INVALID_WRITE_HOOK_RETURN, + severity: ErrorSeverity.InvalidReact, + reason: + 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed', + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_ACCESS_BEFORE_INIT]: { + code: ErrorCode.INVALID_ACCESS_BEFORE_INIT, + severity: ErrorSeverity.InvalidReact, + reason: 'Cannot access variable before it is declared', + description: `Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized`, + linterCategory: LinterCategory.INVALID_WRITE, + }, + [ErrorCode.INVALID_WRITE_GENERIC]: { + code: ErrorCode.INVALID_WRITE_GENERIC, + severity: ErrorSeverity.InvalidReact, + reason: 'This modifies a variable that React considers immutable', + linterCategory: LinterCategory.INVALID_WRITE, + }, + + /** Compiler Config */ + [ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER]: { + code: ErrorCode.DYNAMIC_GATING_IS_NOT_IDENTIFIER, + severity: ErrorSeverity.InvalidReact, + reason: 'Dynamic gating directive is not a valid JavaScript identifier', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES]: { + code: ErrorCode.DYNAMIC_GATING_MULTIPLE_DIRECTIVES, + severity: ErrorSeverity.InvalidReact, + reason: 'Expected a single dynamic gating directive', + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + [ErrorCode.FILENAME_NOT_SET]: { + code: ErrorCode.FILENAME_NOT_SET, + severity: ErrorSeverity.InvalidConfig, + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + linterCategory: LinterCategory.COMPILER_CONFIG, + }, + + /** Effect dependencies */ + [ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES]: { + code: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, + reason: + 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.MEMOIZED_DEPENDENCIES, + }, + + /** Invalid / unsupported syntax */ + [ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS]: { + code: ErrorCode.INVALID_SYNTAX_MULTIPLE_DEFAULTS, + reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST]: { + code: ErrorCode.INVALID_SYNTAX_REASSIGNED_CONST, + reason: `Expect \`const\` declaration not to be reassigned`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL]: { + code: ErrorCode.INVALID_SYNTAX_BAD_VARIABLE_DECL, + reason: `Expected variable declaration to be an identifier if no initializer was provided`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_WITH]: { + code: ErrorCode.UNSUPPORTED_WITH, + reason: `JavaScript 'with' syntax is not supported`, + description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_INNER_CLASS]: { + code: ErrorCode.UNSUPPORTED_INNER_CLASS, + reason: 'Inline `class` declarations are not supported', + description: `Move class declarations outside of components/hooks`, + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_IMPORT_EXPORT]: { + code: ErrorCode.INVALID_IMPORT_EXPORT, + reason: + 'JavaScript `import` and `export` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_TS_NAMESPACE]: { + code: ErrorCode.INVALID_TS_NAMESPACE, + reason: + 'TypeScript `namespace` statements may only appear at the top level of a module', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_NEW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_NEW_EXPRESSION, + reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_EMPTY_SEQUENCE_EXPRESSION, + reason: `Expected sequence expression to have at least one expression`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_QUASI_LENGTHS]: { + code: ErrorCode.INVALID_QUASI_LENGTHS, + reason: `Unexpected quasi and subexpression lengths in template literal`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION]: { + code: ErrorCode.INVALID_SYNTAX_DELETE_EXPRESSION, + reason: `Only object properties can be deleted`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_THROW_EXPRESSION]: { + code: ErrorCode.UNSUPPORTED_THROW_EXPRESSION, + reason: `Throw expressions are not supported`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JSX_NAMESPACED_NAME]: { + code: ErrorCode.INVALID_JSX_NAMESPACED_NAME, + reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.UNSUPPORTED_EVAL]: { + code: ErrorCode.UNSUPPORTED_EVAL, + reason: `The 'eval' function is not supported`, + description: + 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', + severity: ErrorSeverity.UnsupportedJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME]: { + code: ErrorCode.INVALID_SYNTAX_RESERVED_VARIABLE_NAME, + reason: 'Expected a non-reserved identifier name', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.INVALID_JAVASCRIPT_AST]: { + code: ErrorCode.INVALID_JAVASCRIPT_AST, + reason: 'Encountered invalid JavaScript', + severity: ErrorSeverity.InvalidJS, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.BAILOUT_ESLINT_SUPPRESSION]: { + code: ErrorCode.BAILOUT_ESLINT_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_REMOVED]: { + code: ErrorCode.MANUAL_MEMO_REMOVED, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + + /** + * This is left vague as fire is very experimental + */ + [ErrorCode.CANNOT_COMPILE_FIRE]: { + code: ErrorCode.CANNOT_COMPILE_FIRE, + reason: 'Cannot compile `fire`', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.DID_NOT_INFER_DEPS]: { + code: ErrorCode.DID_NOT_INFER_DEPS, + reason: + 'Cannot infer dependencies of this effect. This will break your build!', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + [ErrorCode.BAILOUT_FLOW_SUPPRESSION]: { + code: ErrorCode.BAILOUT_FLOW_SUPPRESSION, + reason: + 'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow', + description: + 'React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior', + severity: ErrorSeverity.InvalidReact, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + + /** Syntax React Compiler may eventually support */ + [ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER]: { + code: ErrorCode.TODO_CONFLICTING_FBT_IDENTIFIER, + reason: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.TODO_DUPLICATE_FBT_TAGS]: { + code: ErrorCode.TODO_DUPLICATE_FBT_TAGS, + reason: 'Support duplicate fbt tags', + severity: ErrorSeverity.Todo, + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.UNKNOWN_FUNCTION_PARAMETERS]: { + code: ErrorCode.UNKNOWN_FUNCTION_PARAMETERS, + severity: ErrorSeverity.Todo, + reason: 'Currently unsupported function parameter syntax', + linterCategory: LinterCategory.TODO_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_MUTATED_LATER]: { + code: ErrorCode.MANUAL_MEMO_MUTATED_LATER, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: [ + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', + 'This dependency may be mutated later, which could cause the value to change unexpectedly.', + ].join(''), + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, + [ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT]: { + code: ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, + reason: + 'Compilation skipped because existing memoization could not be preserved', + description: + 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ' + + 'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected.', + severity: ErrorSeverity.CannotPreserveMemoization, + linterCategory: LinterCategory.UNSUPPORTED_SYNTAX, + }, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts new file mode 100644 index 0000000000..f399cadb9d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/CompilerErrorSeverity.ts @@ -0,0 +1,34 @@ +export enum ErrorSeverity { + /** + * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some + * misunderstanding on the user’s part. + */ + InvalidJS = 'InvalidJS', + /** + * JS syntax that is not supported and which we do not plan to support. Developers should + * rewrite to use supported forms. + */ + UnsupportedJS = 'UnsupportedJS', + /** + * Code that breaks the rules of React. + */ + InvalidReact = 'InvalidReact', + /** + * Incorrect configuration of the compiler. + */ + InvalidConfig = 'InvalidConfig', + /** + * Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve + * memoization. + */ + CannotPreserveMemoization = 'CannotPreserveMemoization', + /** + * Unhandled syntax that we don't support yet. + */ + Todo = 'Todo', + /** + * An unexpected internal error in the compiler that indicates critical issues that can panic + * the compiler. + */ + Invariant = 'Invariant', +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index b28228339c..d04e163960 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -6,11 +6,7 @@ */ import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetail, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerError, CompilerErrorDetail} from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {isHookName} from '../HIR/Environment'; import { @@ -27,6 +23,7 @@ import { } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; import {Result} from '../Utils/Result'; +import {ErrorCode, ErrorCodeDetails} from '../Utils/CompilerErrorCodes'; /** * Represents the possible kinds of value which may be stored at a given Place during @@ -111,8 +108,6 @@ export function validateHooksUsage( // Once a particular hook has a conditional call error, don't report any further issues for this hook setKind(place, Kind.Error); - const reason = - 'Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)'; const previousError = typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; @@ -120,15 +115,15 @@ export function validateHooksUsage( * In some circumstances such as optional calls, we may first encounter a "hook may not be referenced as normal values" error. * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error */ - if (previousError === undefined || previousError.reason !== reason) { + if ( + previousError === undefined || + previousError.reason !== + ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason + ) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason, + CompilerErrorDetail.fromCode(ErrorCode.HOOK_CALL_STATIC, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -139,13 +134,8 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values', + CompilerErrorDetail.fromCode(ErrorCode.HOOK_INVALID_REFERENCE, { loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, }), ); } @@ -156,14 +146,12 @@ export function validateHooksUsage( if (previousError === undefined) { recordError( place.loc, - new CompilerErrorDetail({ - description: null, - reason: - 'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks', - loc: place.loc, - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }), + CompilerErrorDetail.fromCode( + ErrorCodeDetails[ErrorCode.HOOK_CALL_REACTIVE].code, + { + loc: place.loc, + }, + ), ); } } @@ -424,7 +412,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -446,16 +434,10 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { : instr.value.property; const hookKind = getHookKind(fn.env, callee.identifier); if (hookKind != null) { - errors.pushErrorDetail( - new CompilerErrorDetail({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)', - loc: callee.loc, - description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, - suggestions: null, - }), - ); + errors.pushErrorCode(ErrorCode.HOOK_CALL_NOT_TOP_LEVEL, { + loc: callee.loc, + description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`, + }); } break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 31bbf8c94d..b6c3038915 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -13,13 +13,17 @@ import { eachTerminalOperand, } from '../HIR/visitors'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Ok, Result} from '../Utils/Result'; /** * Validates that local variables cannot be reassigned after render. * This prevents a category of bugs in which a closure captures a * binding from one render but does not update */ -export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { +export function validateLocalsNotReassignedAfterRender( + fn: HIRFunction, +): Result { const contextVariables = new Set(); const reassignment = getContextReassignment( fn, @@ -35,9 +39,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -45,8 +47,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { message: `Cannot reassign ${variable} after render completes`, }), ); - throw errors; + return errors.asResult(); } + return Ok(undefined); } function getContextReassignment( @@ -90,9 +93,7 @@ function getContextReassignment( ? `\`${reassignment.identifier.name.value}\`` : 'variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + CompilerDiagnostic.fromCode(ErrorCode.REASSIGN_IN_ASYNC, { description: 'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead', }).withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts index b33cfb1512..90c714c557 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '..'; +import {CompilerError} from '..'; import { Identifier, Instruction, @@ -22,6 +22,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction, } from '../ReactiveScopes/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -108,12 +109,8 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ - reason: - 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', - description: null, - severity: ErrorSeverity.CannotPreserveMemoization, + errorCode: ErrorCode.MEMOIZED_EFFECT_DEPENDENCIES, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, - suggestions: null, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts index 8989cb1ac2..72e237f660 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {CompilerError, EnvironmentConfig} from '..'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateNoCapitalizedCalls( @@ -33,8 +34,6 @@ export function validateNoCapitalizedCalls( const errors = new CompilerError(); const capitalLoadGlobals = new Map(); const capitalizedProperties = new Map(); - const reason = - 'Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config'; for (const [, block] of fn.body.blocks) { for (const {lvalue, value} of block.instructions) { switch (value.kind) { @@ -55,11 +54,9 @@ export function validateNoCapitalizedCalls( const calleeIdentifier = value.callee.identifier.id; const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { - CompilerError.throwInvalidReact({ - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${calleeName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; @@ -78,12 +75,9 @@ export function validateNoCapitalizedCalls( const propertyIdentifier = value.property.identifier.id; const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason, + errors.pushErrorCode(ErrorCode.CAPITALIZED_CALLS, { description: `${propertyName} may be a component.`, loc: value.loc, - suggestions: null, }); } break; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts index d026a94ed4..5ec74f0dae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {CompilerError, SourceLocation} from '..'; import { ArrayExpression, BlockId, @@ -19,6 +19,8 @@ import { eachInstructionValueOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; +import {Result} from '../Utils/Result'; /** * Validates that useEffect is not used for derived computations which could/should @@ -43,7 +45,9 @@ import { * const fullName = firstName + ' ' + lastName; * ``` */ -export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { +export function validateNoDerivedComputationsInEffects( + fn: HIRFunction, +): Result { const candidateDependencies: Map = new Map(); const functions: Map = new Map(); const locals: Map = new Map(); @@ -96,9 +100,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void { } } } - if (errors.hasErrors()) { - throw errors; - } + return errors.asResult(); } function validateEffect( @@ -218,13 +220,6 @@ function validateEffect( } for (const loc of setStateLocations) { - errors.push({ - reason: - 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', - description: null, - severity: ErrorSeverity.InvalidReact, - loc, - suggestions: null, - }); + errors.pushErrorCode(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS, {loc}); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 7a79c74780..f829e4b92a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -5,7 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError, Effect} from '..'; +import {ErrorCode} from '../CompilerError'; import { FunctionEffect, HIRFunction, @@ -65,9 +66,7 @@ export function validateNoFreezingKnownMutableFunctions( ? `\`${place.identifier.name.value}\`` : 'a local variable'; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + CompilerDiagnostic.fromCode(ErrorCode.WRITE_AFTER_RENDER, { description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 85adb79ceb..ee452fc08a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -35,19 +36,13 @@ export function validateNoImpureFunctionsInRender( ); if (signature != null && signature.impure === true) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', - description: - (signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function. ` - : '') + - 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.IMPURE_FUNCTIONS).withDetail({ kind: 'error', loc: callee.loc, - message: 'Cannot call impure function', + message: + signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : 'This is an impure function.', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index eea6c0a08e..bab00d7159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {CompilerDiagnostic, CompilerError} from '..'; import {BlockId, HIRFunction} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -35,11 +36,7 @@ export function validateNoJSXInTryStatement( case 'JsxExpression': case 'JsxFragment': { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', - description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, - }).withDetail({ + CompilerDiagnostic.fromCode(ErrorCode.JSX_IN_TRY).withDetail({ kind: 'error', loc: value.loc, message: 'Avoid constructing JSX within try/catch', diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts index e1c17625f4..1ffc5d013c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { BlockId, HIRFunction, @@ -26,6 +22,7 @@ import { eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Err, Ok, Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -467,11 +464,9 @@ function validateNoRefAccessInRenderImpl( if (fnType.fn.readRefEffect) { didError = true; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.NO_REF_ACCESS_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, message: `This function accesses a ref value`, @@ -730,15 +725,13 @@ function destructure( function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -754,15 +747,13 @@ function validateNoRefValueAccess( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } @@ -780,15 +771,13 @@ function validateNoRefPassedToFunction( (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Passing a ref to a function may read its value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Passing a ref to a function may read its value during render`, + }, + ), ); } } @@ -802,15 +791,13 @@ function validateNoRefUpdate( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: (type.kind === 'RefValue' && type.loc) || loc, - message: `Cannot update ref during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: (type.kind === 'RefValue' && type.loc) || loc, + message: `Cannot update ref during render`, + }, + ), ); } } @@ -823,21 +810,13 @@ function validateNoDirectRefValueAccess( const type = destructure(env.get(operand.identifier.id)); if (type?.kind === 'RefValue') { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', - description: ERROR_DESCRIPTION, - }).withDetail({ - kind: 'error', - loc: type.loc ?? operand.loc, - message: `Cannot access ref value during render`, - }), + CompilerDiagnostic.fromCode(ErrorCode.NO_REF_ACCESS_IN_RENDER).withDetail( + { + kind: 'error', + loc: type.loc ?? operand.loc, + message: `Cannot access ref value during render`, + }, + ), ); } } - -const ERROR_DESCRIPTION = - 'React refs are values that are not needed for rendering. Refs should only be accessed ' + - 'outside of render, such as in event handlers or effects. ' + - 'Accessing a ref value (the `current` property) during render can cause your component ' + - 'not to update as expected (https://react.dev/reference/react/useRef)'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index 9c4efa380c..0a5638277d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -20,6 +16,7 @@ import { Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -95,19 +92,9 @@ export function validateNoSetStateInEffects( const setState = setStateFunctions.get(arg.identifier.id); if (setState !== undefined) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState synchronously within an effect can trigger cascading renders', - description: - 'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' + - 'In general, the body of an effect should do one or both of the following:\n' + - '* Update external systems with the latest state from React.\n' + - '* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' + - 'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' + - '(https://react.dev/learn/you-might-not-need-an-effect)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_EFFECTS, + ).withDetail({ kind: 'error', loc: setState.loc, message: diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index 81209d61c6..dc67540ee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -5,14 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {eachInstructionValueOperand} from '../HIR/visitors'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -127,14 +124,9 @@ function validateNoSetStateInRenderImpl( ) { if (activeManualMemoId !== null) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState from useMemo may trigger an infinite loop', - description: - 'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_MEMO, + ).withDetail({ kind: 'error', loc: callee.loc, message: 'Found setState() within useMemo()', @@ -142,17 +134,12 @@ function validateNoSetStateInRenderImpl( ); } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - category: - 'Calling setState during render may trigger an infinite loop', - description: - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)', - severity: ErrorSeverity.InvalidReact, - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_SET_STATE_IN_RENDER, + ).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() call here', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 6bb59247da..7776e15cbb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError, ErrorCode} from '../CompilerError'; import { DeclarationId, Effect, @@ -280,13 +276,8 @@ function validateInferredDep( } } errorState.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. ', + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_DEPENDENCIES_CONFLICT, { + description: DEBUG || // If the dependency is a named variable then we can report it. Otherwise only print in debug mode (dep.identifier.name != null && dep.identifier.name.kind === 'named') @@ -300,9 +291,6 @@ function validateInferredDep( : 'Inferred dependency not present in source' }.` : '', - ] - .join('') - .trim(), suggestions: null, }).withDetail({ kind: 'error', @@ -534,15 +522,9 @@ class Visitor extends ReactiveFunctionVisitor { !this.prunedScopes.has(identifier.scope.id) ) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ', - 'This dependency may be mutated later, which could cause the value to change unexpectedly.', - ].join(''), - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.MANUAL_MEMO_MUTATED_LATER, + ).withDetail({ kind: 'error', loc, message: 'This dependency may be modified later', @@ -582,18 +564,10 @@ class Visitor extends ReactiveFunctionVisitor { for (const identifier of decls) { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.CannotPreserveMemoization, - category: - 'Compilation skipped because existing memoization could not be preserved', - description: [ - 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ', - DEBUG - ? `${printIdentifier(identifier)} was not memoized.` - : '', - ] - .join('') - .trim(), + CompilerDiagnostic.fromCode(ErrorCode.MANUAL_MEMO_REMOVED, { + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized.` + : '', }).withDetail({ kind: 'error', loc, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts index 7f5fb408b4..12722fa2d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; /** @@ -64,9 +61,7 @@ export function validateStaticComponents( ); if (location != null) { error.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + CompilerDiagnostic.fromCode(ErrorCode.STATIC_COMPONENTS, { description: `Components created during render will reset their state each time they are created. Declare components outside of render. `, }) .withDetail({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index 69ab401c89..05531ff211 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - CompilerDiagnostic, - CompilerError, - ErrorSeverity, -} from '../CompilerError'; +import {CompilerDiagnostic, CompilerError} from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; +import {ErrorCode} from '../Utils/CompilerErrorCodes'; import {Result} from '../Utils/Result'; export function validateUseMemo(fn: HIRFunction): Result { @@ -73,13 +70,9 @@ export function validateUseMemo(fn: HIRFunction): Result { ? firstParam.loc : firstParam.place.loc; errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', - description: - 'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS, + ).withDetail({ kind: 'error', loc, message: 'Callbacks with parameters are not supported', @@ -89,14 +82,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( - CompilerDiagnostic.create({ - severity: ErrorSeverity.InvalidReact, - category: - 'useMemo() callbacks may not be async or generator functions', - description: - 'useMemo() callbacks are called once and must synchronously return a value.', - suggestions: null, - }).withDetail({ + CompilerDiagnostic.fromCode( + ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC, + ).withDetail({ kind: 'error', loc: body.loc, message: 'Async and generator functions are not supported', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index ce42e65125..e47107723f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | return ; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index ee57ea6eb0..5acfcde730 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -22,13 +22,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 8476885de7..eb2516e386 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -18,13 +18,15 @@ function Component() { ``` Found 1 error: -Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + | ^^^^^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | return
; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 6e522e1666..a5530a1180 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -20,7 +20,7 @@ Found 1 error: 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. Found suppression `$FlowFixMe[react-rule-hook]` +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]` error.bailout-on-flow-suppression.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 3221f97731..b348bc6191 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -23,7 +23,7 @@ Found 2 errors: 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. Found suppression `eslint-disable my-app/react-rule` +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` error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] @@ -36,7 +36,7 @@ error.bailout-on-suppression-of-custom-rule.ts:3:0 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. Found suppression `eslint-disable-next-line my-app/react-rule` +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` error.bailout-on-suppression-of-custom-rule.ts:7:2 5 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index 6e9887c5ac..d6e0a07d7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index e5c28e6e36..71f96f1ab5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index a8a83f6b11..ef6f6091a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -17,7 +17,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index 4b49c5f653..96485fb584 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -17,12 +17,12 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `x` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ `x` cannot be reassigned + | ^ `x` should not be reassigned 3 | return {x}; 4 | } 5 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index 6da3b558bd..bae0df94af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -19,13 +19,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 4 | 5 | return [a, b]; 6 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 8e8b7917d7..f3f2cff6e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -39,13 +39,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `someGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ `someGlobal` cannot be reassigned + | ^^^^^^^^^^ `someGlobal` should not be reassigned 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 291d3873b4..8c512ae350 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -42,13 +42,13 @@ Found 1 error: Error: Cannot access variable before it is declared -`setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ `setState` accessed before it is declared + | ^^^^^^^^ `setState` is accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 3155d64329..cf6b33ce8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index 4ac7af5bae..768e0300bf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -23,7 +23,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 437fbcb38c..de59216e60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -48,7 +48,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md index 25b9a5c186..72bfa49422 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-const.expect.md @@ -15,7 +15,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign a `const` variable +Error: Expect `const` declaration not to be reassigned `x` is declared as const. @@ -23,7 +23,7 @@ error.invalid-reassign-const.ts:3:2 1 | function Component() { 2 | const x = 0; > 3 | x = 1; - | ^ Cannot reassign a `const` variable + | ^ Expect `const` declaration not to be reassigned 4 | } 5 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..1b5942449b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -17,7 +17,7 @@ function useFoo() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..8a27203097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -49,7 +49,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..309fd02906 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -50,7 +50,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..1b4f26b2c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,7 +43,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 96be8584be..14d6068b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -21,7 +21,7 @@ Found 2 errors: 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. Found suppression `eslint-disable react-hooks/rules-of-hooks` +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` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ @@ -32,7 +32,7 @@ error.invalid-sketchy-code-use-forget.ts:1:0 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. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` +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` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index e19cee7532..d77327b90d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -40,7 +40,7 @@ Found 1 error: 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. Found suppression `eslint-disable react-hooks/rules-of-hooks` +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` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index fa9e20a418..f10764dc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ Found setState() within useMemo() + | ^^^^ Found setState() call here 7 | aliased(2); 8 | 9 | return x; @@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ Found setState() within useMemo() + | ^^^^^^^ Found setState() call here 8 | 9 | return x; 10 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..db7b07261e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -34,7 +34,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md index 78530caf4a..9903cf2a1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutate-property-from-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.mutate-property-from-global.ts:4:9 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md index c7b1ac5f45..508aea8d27 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.not-useEffect-external-mutate.expect.md @@ -21,7 +21,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:5:4 3 | function Component(props) { @@ -34,7 +34,7 @@ error.not-useEffect-external-mutate.ts:5:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md index 89bcedf956..606c1c42c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md @@ -24,13 +24,15 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Modifying a variable defined outside a component or hook is not allowed. Consider using an effect +Error: Cannot reassign variables declared outside of the component/hook + +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). error.object-capture-global-mutation.ts:4:4 2 | function Foo() { 3 | const x = () => { > 4 | window.href = 'foo'; - | ^^^^^^ Modifying a variable defined outside a component or hook is not allowed. Consider using an effect + | ^^^^^^ Cannot reassign variables declared outside of the component/hook 5 | }; 6 | const y = {x}; 7 | return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md index 2c409ea5b5..cd7f202a1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassign-global-fn-arg.expect.md @@ -28,13 +28,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `b` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassign-global-fn-arg.ts:5:4 3 | export default function MyApp() { 4 | const fn = () => { > 5 | b = 2; - | ^ `b` cannot be reassigned + | ^ `b` should not be reassigned 6 | }; 7 | return foo(fn); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md index 8835f19ad1..6963940109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global-indirect.expect.md @@ -21,26 +21,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:4:4 2 | const foo = () => { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | }; 7 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | }; 7 | foo(); 8 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md index 4d259dd8d4..88ae3a7ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.reassignment-to-global.expect.md @@ -18,26 +18,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:3:2 1 | function Component() { 2 | // Cannot assign to globals > 3 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 4 | moduleLocal = true; 5 | } 6 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | // Cannot assign to globals 3 | someUnknownGlobal = true; > 4 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 5 | } 6 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 9c87cafff1..0bcfae2e9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -24,7 +24,7 @@ Found 1 error: 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. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` +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` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7077b733b0..bd1b3ed4c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -25,7 +25,7 @@ Found 1 error: 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. Found suppression `eslint-disable react-hooks/rules-of-hooks` +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` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md index 7ffe3f84cf..544532b3b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.store-property-in-global.expect.md @@ -19,7 +19,7 @@ Found 1 error: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.store-property-in-global.ts:4:2 2 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..f4d192530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -19,7 +19,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index 3d54bcd75c..6f9da8fda9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index c892066bf8..6ab4ecb36e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 7 | return state; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index a617a2f572..69c81c3ff8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ Found setState() within useMemo() + | ^^^^^^^^ Found setState() call here 12 | return state; 13 | } 14 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index dfb5c7b56f..79369f3608 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 9 | 10 | return [x]; 11 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index f03b514c3f..c8a775499a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ Found setState() within useMemo() + | ^^^ Found setState() call here 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md index 8432be198b..cfd27c7356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.update-global-should-bailout.expect.md @@ -23,13 +23,13 @@ Found 1 error: Error: Cannot reassign variables declared outside of the component/hook -Variable `renderCount` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.update-global-should-bailout.ts:3:2 1 | let renderCount = 0; 2 | function useFoo() { > 3 | renderCount += 1; - | ^^^^^^^^^^^^^^^^ `renderCount` cannot be reassigned + | ^^^^^^^^^^^^^^^^ `renderCount` should not be reassigned 4 | return renderCount; 5 | } 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md index 188814ee02..1531425c20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.useMemo-non-literal-depslist.expect.md @@ -30,9 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Expected the dependency list for useMemo to be an array literal - -Expected the dependency list for useMemo to be an array literal +Error: Expected the dependency list of useMemo or useCallback to be an array literal error.useMemo-non-literal-depslist.ts:10:4 8 | return text.toUpperCase(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md index d8250c6f57..b4f642df43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-invalid-multiple.expect.md @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} +{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"options":{"reason":"Expected a single dynamic gating directive","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md index 08eb396bb1..cf7a8cad85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":333},"end":{"line":11,"column":43,"index":357},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b629bfef27..0925a9b982 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index c16890e52e..3e3135929b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","severity":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Calling setState synchronously within an effect can trigger cascading renders","description":"Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect)","details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() directly within an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index a9782a3b9e..a6f268555c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -19,41 +19,41 @@ function Component() { ``` Found 3 errors: -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^ `Date.now` is an impure function. 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^^^^^ `performance.now` is an impure function. 6 | const rand = Math.random(); 7 | return ; 8 | } -Error: Cannot call impure function during render +Error: Cannot call impure functions during render -`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) +Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ Cannot call impure function + | ^^^^^^^^^^^^^ `Math.random` is an impure function. 7 | return ; 8 | } 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..96b1d866fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -44,7 +44,7 @@ function Component() { ``` Found 1 error: -Error: Cannot reassign variable after render completes +Error: Cannot modify local variables after render completes Reassigning `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..bef031723c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -35,12 +35,12 @@ Found 1 error: Error: Cannot access variable before it is declared -`data` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. +Reading a variable before it is initialized will prevent the earlier access from updating when this value changes over time. Instead, move the variable access to after it has been initialized 9 | // TDZ violation! 10 | const onRefetch = useCallback(() => { > 11 | refetch(data); - | ^^^^ `data` accessed before it is declared + | ^^^^ `data` is accessed before it is declared 12 | }, [refetch]); 13 | 14 | // The context variable gets frozen here since it's passed to a hook diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md index 80a12e5d40..f1cc5c4808 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.not-useEffect-external-mutate.expect.md @@ -22,7 +22,7 @@ Found 2 errors: Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:6:4 4 | function Component(props) { @@ -35,7 +35,7 @@ error.not-useEffect-external-mutate.ts:6:4 Error: This value cannot be modified -Modifying a variable defined outside a component or hook is not allowed. Consider using an effect. +Cannot reassign variables declared outside of the component/hook. error.not-useEffect-external-mutate.ts:7:4 5 | foo(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md index 41ed513912..bb4d91a4bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global-indirect.expect.md @@ -22,26 +22,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:5:4 3 | const foo = () => { 4 | // Cannot assign to globals > 5 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 6 | moduleLocal = true; 7 | }; 8 | foo(); Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global-indirect.ts:6:4 4 | // Cannot assign to globals 5 | someUnknownGlobal = true; > 6 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 7 | }; 8 | foo(); 9 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md index 6089255fd5..84339b0759 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.reassignment-to-global.expect.md @@ -19,26 +19,26 @@ Found 2 errors: Error: Cannot reassign variables declared outside of the component/hook -Variable `someUnknownGlobal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:4:2 2 | function Component() { 3 | // Cannot assign to globals > 4 | someUnknownGlobal = true; - | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` cannot be reassigned + | ^^^^^^^^^^^^^^^^^ `someUnknownGlobal` should not be reassigned 5 | moduleLocal = true; 6 | } 7 | Error: Cannot reassign variables declared outside of the component/hook -Variable `moduleLocal` is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) +Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.reassignment-to-global.ts:5:2 3 | // Cannot assign to globals 4 | someUnknownGlobal = true; > 5 | moduleLocal = true; - | ^^^^^^^^^^^ `moduleLocal` cannot be reassigned + | ^^^^^^^^^^^ `moduleLocal` should not be reassigned 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md index 2e0c890367..8441e79925 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/retry-no-emit.expect.md @@ -54,10 +54,11 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook.","details":[{"kind":"error","loc":{"start":{"line":12,"column":2,"index":404},"end":{"line":12,"column":6,"index":408},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"value cannot be modified"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr2` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.","details":[{"kind":"error","loc":{"start":{"line":11,"column":19,"index":365},"end":{"line":11,"column":43,"index":389},"filename":"retry-no-emit.ts"},"message":"This function may (indirectly) reassign or modify `arr2` after render"},{"kind":"error","loc":{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},"message":"This modifies `arr2`"}]}},"fnLoc":null} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":280},"end":{"line":8,"column":46,"index":324},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":309},"end":{"line":8,"column":34,"index":312},"filename":"retry-no-emit.ts","identifierName":"arr"}]} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":9,"memoBlocks":5,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md index 27af59e175..f433f8cb88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.validate-useMemo-named-function.expect.md @@ -24,8 +24,6 @@ Found 1 error: Error: Expected the first argument to be an inline function expression -Expected the first argument to be an inline function expression - error.validate-useMemo-named-function.ts:9:20 7 | // for now. 8 | function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 006d2a49c0..bda9c3b694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -31,7 +31,7 @@ function Component({prop1}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 8481ed2c57..9604f69476 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -15,7 +15,7 @@ console.log(fire == null); ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index f84686bc36..02753ce345 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -32,7 +32,7 @@ function Component({props, bar}) { ``` Found 1 error: -Error: [Fire] Untransformed reference to compiler-required feature. +Error: Cannot compile `fire` null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 81c36a362c..cdb0726f2e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -26,7 +26,7 @@ function Component({props, bar}) { ``` Found 2 errors: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -39,7 +39,7 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 96cea9c08f..d82f1ee1b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -27,7 +27,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 4dc5336ebe..a841813630 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -30,7 +30,7 @@ function Component(props) { ``` Found 1 error: -Invariant: Cannot compile `fire` +Error: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts new file mode 100644 index 0000000000..c3d6704504 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,32 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoImpureFunctionCallsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no impure function calls rule', NoImpureFunctionCallsRule, { + valid: [], + invalid: [ + { + name: 'Known impure function calls are caught', + code: normalizeIndent` + function Component() { + const date = Date.now(); + const now = performance.now(); + const rand = Math.random(); + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + makeTestCaseError(ErrorCode.IMPURE_FUNCTIONS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts new file mode 100644 index 0000000000..9192087b31 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,85 @@ +/** + * 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 {RulesOfHooksRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('rules-of-hooks', RulesOfHooksRule, { + valid: [ + { + name: 'Basic example', + code: normalizeIndent` + function Component() { + useHook(); + return
Hello world
; + } + `, + }, + { + name: 'Violation with Flow suppression', + code: ` + // Valid since error already suppressed with flow. + function useHook() { + if (cond) { + // $FlowFixMe[react-rule-hook] + useConditionalHook(); + } + } + `, + }, + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts new file mode 100644 index 0000000000..8463e860df --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,31 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoAmbiguousJsxRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ambiguous JSX rule', NoAmbiguousJsxRule, { + valid: [], + invalid: [ + { + name: 'JSX in try blocks are warned against', + code: normalizeIndent` + function Component(props) { + let el; + try { + el = ; + } catch { + return null; + } + return el; + } + `, + errors: [makeTestCaseError(ErrorCode.JSX_IN_TRY)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts new file mode 100644 index 0000000000..821cab8007 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {NoCapitalizedCallsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidCapitalizedCallsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('no-capitalized-calls', NoCapitalizedCallsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [invalidCapitalizedCallsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + import Child1 from './Child1'; + import MyModule from './MyModule'; + function Component() { + return <> + {Child1()} + {MyModule.Child2()} + ; + }`, + errors: [ + invalidCapitalizedCallsRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts new file mode 100644 index 0000000000..a8498d303f --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,27 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoRefAccessInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no ref access in render rule', NoRefAccessInRenderRule, { + valid: [], + invalid: [ + { + name: 'validate against simple ref access in render', + code: normalizeIndent` + function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_REF_ACCESS_IN_RENDER)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts new file mode 100644 index 0000000000..43ec6ad010 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInEffects-tests.ts @@ -0,0 +1,48 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {NoSetStateInEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in effects rule', NoSetStateInEffectsRule, { + valid: [], + invalid: [ + { + name: 'unconditional setState in useEffect', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + setState(s => s + 1); + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + { + name: 'conditional setState in render', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function Component() { + const [state, setState] = useState(0); + useEffect(() => { + if (state % 2 === 0) { + setState(state + 1); + } + }); + return state; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts new file mode 100644 index 0000000000..77ce895339 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoSetStateInRender-test.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {ValidateSetStateInRenderRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('no set state in render rule', ValidateSetStateInRenderRule, { + valid: [], + invalid: [ + { + name: 'setState in useMemo', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + return item; + }, [cond, item, init]); + + return ; + } + `, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'unconditional setState in render', + code: normalizeIndent` + function Component(props) { + const [x, setX] = useState(0); + const aliased = setX; + + setX(1); + aliased(2); + + return x; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_RENDER), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts new file mode 100644 index 0000000000..77f6dd93fb --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoUnusedDirectivesRule-test.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {NoUnusedDirectivesRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule} from './shared-utils'; + +testRule('no unused directives rule', NoUnusedDirectivesRule, { + valid: [], + invalid: [ + { + name: "Unused 'use no forget' directive is reported when no errors are present on components", + code: normalizeIndent` + function Component() { + 'use no forget'; + return
Hello world
+ } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction Component() {\n \n return
Hello world
\n}\n', + }, + ], + }, + ], + }, + + { + name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", + code: normalizeIndent` + function notacomponent() { + 'use no forget'; + return 1 + 1; + } + `, + errors: [ + { + message: "Unused 'use no forget' directive", + suggestions: [ + { + output: + // yuck + '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', + }, + ], + }, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts new file mode 100644 index 0000000000..c0e0cf07c9 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,172 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; + +testRule('plugin-recommended', TestRecommendedRules, { + valid: [ + { + name: 'Basic example with component syntax', + code: normalizeIndent` + export default component HelloWorld( + text: string = 'Hello!', + onClick: () => void, + ) { + return
{text}
; + } + `, + }, + + { + // OK because invariants are only meant for the compiler team's consumption + name: '[Invariant] Defined after use', + code: normalizeIndent` + function Component(props) { + let y = function () { + m(x); + }; + + let x = { a }; + m(x); + return y; + } + `, + }, + { + name: "Classes don't throw", + code: normalizeIndent` + class Foo { + #bar() {} + } + `, + }, + ], + invalid: [ + { + name: 'Multiple diagnostic kinds from the same function are surfaced', + code: normalizeIndent` + import Child from './Child'; + function Component() { + const result = cond ?? useConditionalHook(); + return <> + {Child(result)} + ; + } + `, + errors: [ + invalidHookRuleErrorMessage, + invalidCapitalizedCallsRuleErrorMessage, + ], + }, + { + name: 'Multiple diagnostics within the same file are surfaced', + code: normalizeIndent` + function useConditional1() { + 'use memo'; + return cond ?? useConditionalHook(); + } + function useConditional2(props) { + 'use memo'; + return props.cond && useConditionalHook(); + }`, + errors: [invalidHookRuleErrorMessage, invalidHookRuleErrorMessage], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [invalidHookRuleErrorMessage], + }, + { + name: 'Multiple non-fatal useMemo diagnostics are surfaced', + code: normalizeIndent` + import {useMemo, useState} from 'react'; + + function Component({item, cond}) { + const [prevItem, setPrevItem] = useState(item); + const [state, setState] = useState(0); + + useMemo(() => { + if (cond) { + setPrevItem(item); + setState(0); + } + }, [cond, item, init]); + + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_RETURN), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + makeTestCaseError(ErrorCode.INVALID_SET_STATE_IN_MEMO), + ], + }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + import {AUTODEPS} from 'react'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b), AUTODEPS); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + autodepsIndex: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, + ], +}); + +testRule('rules that are not enabled do not error', StaticComponentsRule, { + valid: [ + { + name: 'simple case', + code: normalizeIndent` + function useConditional() { + if (cond) { + useConditionalHook(); + } + } + `, + }, + ], + invalid: [], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index bff40e9649..0000000000 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {ErrorSeverity} from 'babel-plugin-react-compiler/src'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)![0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: ESLintTester.ValidTestCase[]; - invalid: ESLintTester.InvalidTestCase[]; -}; - -const tests: CompilerTestCases = { - valid: [ - { - name: 'Basic example', - code: normalizeIndent` - function foo(x, y) { - if (x) { - return foo(false, y); - } - return [y * 10]; - } - `, - }, - { - name: 'Violation with Flow suppression', - code: ` - // Valid since error already suppressed with flow. - function useHookWithHook() { - if (cond) { - // $FlowFixMe[react-rule-hook] - useConditionalHook(); - } - } - `, - }, - { - name: 'Basic example with component syntax', - code: normalizeIndent` - export default component HelloWorld( - text: string = 'Hello!', - onClick: () => void, - ) { - return
{text}
; - } - `, - }, - { - name: 'Unsupported syntax', - code: normalizeIndent` - function foo(x) { - var y = 1; - return y * x; - } - `, - }, - { - // OK because invariants are only meant for the compiler team's consumption - name: '[Invariant] Defined after use', - code: normalizeIndent` - function Component(props) { - let y = function () { - m(x); - }; - - let x = { a }; - m(x); - return y; - } - `, - }, - { - name: "Classes don't throw", - code: normalizeIndent` - class Foo { - #bar() {} - } - `, - }, - ], - invalid: [ - { - name: 'Reportable levels can be configured', - options: [{reportableLevels: new Set([ErrorSeverity.Todo])}], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: '[InvalidReact] ESlint suppression', - // Indentation is intentionally weird so it doesn't add extra whitespace - code: normalizeIndent` - function Component(props) { - // eslint-disable-next-line react-hooks/rules-of-hooks - return
{props.foo}
; - }`, - errors: [ - { - message: /React Compiler has skipped optimizing this component/, - suggestions: [ - { - output: normalizeIndent` - function Component(props) { - - return
{props.foo}
; - }`, - }, - ], - }, - { - message: - "Definition for rule 'react-hooks/rules-of-hooks' was not found.", - }, - ], - }, - { - name: 'Multiple diagnostics are surfaced', - options: [ - { - reportableLevels: new Set([ - ErrorSeverity.Todo, - ErrorSeverity.InvalidReact, - ]), - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - } - function Bar(props) { - props.a.b = 2; - return
{props.c}
- }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - { - message: /Modifying component props or hook arguments is not allowed/, - }, - ], - }, - { - name: 'Test experimental/unstable report all bailouts mode', - options: [ - { - reportableLevels: new Set([ErrorSeverity.InvalidReact]), - __unstable_donotuse_reportAllBailouts: true, - }, - ], - code: normalizeIndent` - function Foo(x) { - var y = 1; - return
{y * x}
; - }`, - errors: [ - { - message: /Handle var kinds in VariableDeclaration/, - }, - ], - }, - { - name: "'use no forget' does not disable eslint rule", - code: normalizeIndent` - let count = 0; - function Component() { - 'use no forget'; - count = count + 1; - return
Hello world {count}
- } - `, - errors: [ - { - message: - /Cannot reassign variables declared outside of the component\/hook/, - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on components", - code: normalizeIndent` - function Component() { - 'use no forget'; - return
Hello world
- } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction Component() {\n \n return
Hello world
\n}\n', - }, - ], - }, - ], - }, - { - name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks", - code: normalizeIndent` - function notacomponent() { - 'use no forget'; - return 1 + 1; - } - `, - errors: [ - { - message: "Unused 'use no forget' directive", - suggestions: [ - { - output: - // yuck - '\nfunction notacomponent() {\n \n return 1 + 1;\n}\n', - }, - ], - }, - ], - }, - { - name: 'Pipeline errors are reported', - code: normalizeIndent` - import useMyEffect from 'useMyEffect'; - import {AUTODEPS} from 'react'; - function Component({a}) { - 'use no memo'; - useMyEffect(() => console.log(a.b), AUTODEPS); - return
Hello world
; - } - `, - options: [ - { - environment: { - inferEffectDependencies: [ - { - function: { - source: 'useMyEffect', - importSpecifierName: 'default', - }, - autodepsIndex: 1, - }, - ], - }, - }, - ], - errors: [ - { - message: /Cannot infer dependencies of this effect/, - }, - ], - }, - ], -}; - -const eslintTester = new ESLintTester({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 5a2bea6852..87baf724e1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,22 +6,11 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompilerRule'; - -/** - * A string template tag that removes padding from the left side of multi-line strings - * @param {Array} strings array of code strings (only one expected) - */ -function normalizeIndent(strings: TemplateStringsArray): string { - const codeLines = strings[0].split('\n'); - const leftPadding = codeLines[1].match(/\s+/)[0]; - return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); -} - -type CompilerTestCases = { - valid: RuleTester.ValidTestCase[]; - invalid: RuleTester.InvalidTestCase[]; -}; +import { + CompilerTestCases, + normalizeIndent, + TestRecommendedRules, +} from './shared-utils'; const tests: CompilerTestCases = { valid: [ @@ -70,6 +59,7 @@ const tests: CompilerTestCases = { }; const eslintTester = new RuleTester({ + // @ts-ignore[2353] - outdated types parser: require.resolve('@typescript-eslint/parser'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', TestRecommendedRules, tests); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts new file mode 100644 index 0000000000..b2f4b54994 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/StaticComponentsRule-test.ts @@ -0,0 +1,44 @@ +/** + * 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 {StaticComponentsRule} from '../src/rules/ReactCompilerRule'; +import { + normalizeIndent, + invalidStaticComponentsRuleErrorMessage, + testRule, +} from './shared-utils'; + +testRule('static-components', StaticComponentsRule, { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + function Example(props) { + const Component = new ComponentFactory(); + return ; + } + `, + errors: [invalidStaticComponentsRuleErrorMessage], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function Example(props) { + const Component1 = new ComponentFactory(); + const Component2 = new ComponentFactory(); + return <> + + + ; + }`, + errors: [ + invalidStaticComponentsRuleErrorMessage, + invalidStaticComponentsRuleErrorMessage, + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts new file mode 100644 index 0000000000..8d7f481f15 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/UnnecessaryEffects-tests.ts @@ -0,0 +1,36 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UnnecessaryEffectsRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('unnecessary effects rule', UnnecessaryEffectsRule, { + valid: [], + invalid: [ + { + name: 'test case from React docs', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + // https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state + function Form() { + const [firstName, setFirstName] = useState('Taylor'); + const [lastName, setLastName] = useState('Swift'); + + // 🔴 Avoid: redundant state and unnecessary Effect + const [fullName, setFullName] = useState(''); + useEffect(() => { + setFullName(capitalize(firstName + ' ' + lastName)); + }, [firstName, lastName]); + + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.NO_DERIVED_COMPUTATIONS_IN_EFFECTS)], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts new file mode 100644 index 0000000000..85c937a2de --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ValidateUseMemo-test.ts @@ -0,0 +1,43 @@ +/** + * 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 {ErrorCode} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import {UseMemoRule} from '../src/rules/ReactCompilerRule'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; + +testRule('use memo rule', UseMemoRule, { + valid: [], + invalid: [ + { + name: 'Simple async violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component({a, b}) { + let x = useMemo(async () => { + await a; + }, []); + return ; + } + `, + errors: [makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_ASYNC)], + }, + { + name: 'Simple parameters violation', + code: normalizeIndent` + import {useMemo} from 'react'; + + function Component() { + let x = useMemo(c => a, []); + return ; + }`, + errors: [ + makeTestCaseError(ErrorCode.INVALID_USE_MEMO_CALLBACK_PARAMETERS), + ], + }, + ], +}); diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..f9553730e4 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,98 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import { + ErrorCode, + ErrorCodeDetails, +} from 'babel-plugin-react-compiler/src/Utils/ErrorCodes'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; + +/** + * A string template tag that removes padding from the left side of multi-line strings + * @param {Array} strings array of code strings (only one expected) + */ +export function normalizeIndent(strings: TemplateStringsArray): string { + const codeLines = strings[0].split('\n'); + const leftPadding = codeLines[1].match(/\s+/)![0]; + return codeLines.map(line => line.slice(leftPadding.length)).join('\n'); +} + +export type CompilerTestCases = { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; +}; + +export const invalidHookRuleErrorMessage: ESLintTester.TestCaseError = { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.HOOK_CALL_STATIC].reason), + ), +}; + +export const invalidCapitalizedCallsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.CAPITALIZED_CALLS].reason), + ), + }; + +export const invalidStaticComponentsRuleErrorMessage: ESLintTester.TestCaseError = + { + message: new RegExp( + escape(ErrorCodeDetails[ErrorCode.STATIC_COMPONENTS].reason), + ), + }; + +export function makeTestCaseError(code: ErrorCode): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(ErrorCodeDetails[code].reason)), + }; +} + +export function testRule( + name: string, + rule: Rule.RuleModule, + tests: { + valid: ESLintTester.ValidTestCase[]; + invalid: ESLintTester.InvalidTestCase[]; + }, +): void { + const eslintTester = new ESLintTester({ + // @ts-ignore[2353] - outdated types + parser: require.resolve('hermes-eslint'), + parserOptions: { + ecmaVersion: 2015, + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }, + }); + + eslintTester.run(name, rule, tests); +} + +/** + * Aggregates all recommended rules from the plugin. + */ +export const TestRecommendedRules: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Disallow capitalized function calls', + category: 'Possible Errors', + recommended: true, + }, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context) { + for (const rule of Object.values( + configs.recommended.plugins['react-compiler'].rules, + )) { + const listener = rule.create(context); + if (Object.entries(listener).length !== 0) { + throw new Error('TODO: handle rules that return listeners to eslint'); + } + } + return {}; + }, +}; + +test('no test', () => {}); diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index 2dd191f033..e5402611e2 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -24,11 +24,13 @@ "@babel/preset-typescript": "^7.18.6", "@babel/types": "^7.26.0", "@types/eslint": "^8.56.12", + "@types/jest": "^30.0.0", "@types/node": "^20.2.5", "babel-jest": "^29.0.3", "eslint": "8.57.0", "hermes-eslint": "^0.25.1", - "jest": "^29.5.0" + "jest": "^29.5.0", + "regexp.escape": "^2.0.1" }, "engines": { "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" diff --git a/compiler/packages/eslint-plugin-react-compiler/src/index.ts b/compiler/packages/eslint-plugin-react-compiler/src/index.ts index a3577a101e..681e9844ef 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,61 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import * as rules from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, +/** + * Validates React components and hooks follow rules of React + * and follow best practices + */ +const validationRules = { + 'rules-of-hooks': rules.RulesOfHooksRule, + 'no-capitalized-calls': rules.NoCapitalizedCallsRule, + 'no-unstable-components': rules.StaticComponentsRule, + 'validate-use-memo-callbacks': rules.UseMemoRule, + 'validate-writes': rules.InvalidWritesRule, + 'no-unsafe-refs': rules.UnsafeRefsRule, + 'validate-set-state-in-render': rules.ValidateSetStateInRenderRule, + 'no-set-state-in-effects': rules.NoSetStateInEffectsRule, + 'no-ref-access-in-render': rules.NoRefAccessInRenderRule, + 'no-impure-function-calls': rules.NoImpureFunctionCallsRule, + 'unnecessary-effects': rules.UnnecessaryEffectsRule, + 'no-ambiguous-jsx': rules.NoAmbiguousJsxRule, +}; + +const recommendedRules = { + ...validationRules, + 'no-unsupported-syntax': rules.NoUnsupportedSyntaxRule, + + /** Validation for React Compiler inline configuration */ + 'no-unused-directives': rules.NoUnusedDirectivesRule, + 'validate-compiler-config': rules.ValidateCompilerConfigRule, +}; + +const allRules = { + ...recommendedRules, + /** Warn on syntax that React Compiler cannot transform */ + 'no-todo-syntax': rules.WarnOnTodoSyntaxRule, + 'warn-on-compilation-failures': rules.WarnOnUnactionableFailuresRule, }; const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: recommendedRules, }, }, - rules: { - 'react-compiler/react-compiler': 'error' as const, - }, + rules: Object.fromEntries( + Object.keys(recommendedRules).map(ruleName => [ + 'react-compiler/' + ruleName, + 'error', + ]), + ) as Record, }, }; -export {configs, rules, meta}; +export {configs, allRules as rules, meta}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 730b6ff6f8..0058464090 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -5,43 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerDiagnostic, +import { CompilerDiagnosticOptions, - CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; +import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler'; +import {LinterCategory} from 'babel-plugin-react-compiler/src/CompilerError'; function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } -const DEFAULT_REPORTABLE_LEVELS = new Set([ - ErrorSeverity.InvalidReact, - ErrorSeverity.InvalidJS, -]); -let reportableLevels = DEFAULT_REPORTABLE_LEVELS; - -function isReportableDiagnostic( - detail: CompilerErrorDetail | CompilerDiagnostic, -): boolean { - return reportableLevels.has(detail.severity); -} - function makeSuggestions( detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { @@ -95,28 +72,97 @@ function makeSuggestions( return suggest; } -const COMPILER_OPTIONS: Partial = { - noEmit: true, - panicThreshold: 'none', - // Don't emit errors on Flow suppressions--Flow already gave a signal - flowSuppressions: false, - environment: validateEnvironmentConfig({ - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - }), -}; +function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry { + // Compat with older versions of eslint + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + const userOpts = context.options[0] ?? {}; -const rule: Rule.RuleModule = { + const results = runReactCompiler({ + sourceCode, + filename, + userOpts, + }); + + return results; +} + +function hasFlowSuppression( + program: RunCacheEntry, + nodeLoc: BabelSourceLocation, + suppressions: Array, +): boolean { + for (const commentNode of program.flowSuppressions) { + if ( + suppressions.includes(commentNode.code) && + commentNode.line === nodeLoc.start.line - 1 + ) { + return true; + } + } + return false; +} + +function makeRule(filter: Array): Rule.RuleModule['create'] { + return (context: Rule.RuleContext): Rule.RuleListener => { + const result = getReactCompilerResult(context); + + for (const event of result.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if ( + detail.linterCategory != null && + filter.includes(detail.linterCategory) + ) { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { + continue; + } + if ( + hasFlowSuppression(result, loc, [ + 'react-rule-hook', + 'react-rule-unsafe-ref', + ]) + ) { + // If Flow already caught this error, we don't need to report it again. + continue; + } + // TODO: if multiple rules report the same linter category, + // we should deduplicate them with a "reported" set + context.report({ + message: detail.printErrorMessage(result.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); + } + } + } + return {}; + }; +} + +const unactionableBailouts: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Surfaces diagnostics from React Forget', + description: 'Surfaces unactionable compilation bailouts', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + }, + create(context: Rule.RuleContext): Rule.RuleListener { + return {}; + }, +}; + +export const RulesOfHooksRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to the rules of hooks', recommended: true, }, fixable: 'code', @@ -124,234 +170,284 @@ const rule: Rule.RuleModule = { // validation is done at runtime with zod schema: [{type: 'object', additionalProperties: true}], }, - create(context: Rule.RuleContext) { - // Compat with older versions of eslint - const sourceCode = context.sourceCode ?? context.getSourceCode(); - const filename = context.filename ?? context.getFilename(); - const userOpts = context.options[0] ?? {}; - if ( - userOpts.reportableLevels != null && - userOpts.reportableLevels instanceof Set - ) { - reportableLevels = userOpts.reportableLevels; - } else { - reportableLevels = DEFAULT_REPORTABLE_LEVELS; - } - /** - * Experimental setting to report all compilation bailouts on the compilation - * unit (e.g. function or hook) instead of the offensive line. - * Intended to be used when a codebase is 100% reliant on the compiler for - * memoization (i.e. deleted all manual memo) and needs compilation success - * signals for perf debugging. - */ - let __unstable_donotuse_reportAllBailouts: boolean = false; - if ( - userOpts.__unstable_donotuse_reportAllBailouts != null && - typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' - ) { - __unstable_donotuse_reportAllBailouts = - userOpts.__unstable_donotuse_reportAllBailouts; - } + create: makeRule([LinterCategory.RULES_OF_HOOKS]), +}; - let shouldReportUnusedOptOutDirective = true; - const options: PluginOptions = parsePluginOptions({ - ...COMPILER_OPTIONS, - ...userOpts, - environment: { - ...COMPILER_OPTIONS.environment, - ...userOpts.environment, - }, - }); - const userLogger: Logger | null = options.logger; - options.logger = { - logEvent: (eventFilename, event): void => { - userLogger?.logEvent(eventFilename, event); - if (event.kind === 'CompileError') { - shouldReportUnusedOptOutDirective = false; - const detail = event.detail; - const suggest = makeSuggestions(detail.options); - if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { - const loc = detail.primaryLocation(); - const locStr = - loc != null && typeof loc !== 'symbol' - ? ` (@:${loc.start.line}:${loc.start.column})` - : ''; - /** - * Report bailouts with a smaller span (just the first line). - * Compiler bailout lints only serve to flag that a react function - * has not been optimized by the compiler for codebases which depend - * on compiler memo heavily for perf. These lints are also often not - * actionable. - */ - let endLoc; - if (event.fnLoc.end.line === event.fnLoc.start.line) { - endLoc = event.fnLoc.end; - } else { - endLoc = { - line: event.fnLoc.start.line, - // Babel loc line numbers are 1-indexed - column: - sourceCode.text.split(/\r?\n|\r|\n/g)[ - event.fnLoc.start.line - 1 - ]?.length ?? 0, - }; - } - const firstLineLoc = { - start: event.fnLoc.start, - end: endLoc, - }; - context.report({ - message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, - loc: firstLineLoc, - suggest, - }); - } +export const NoCapitalizedCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to capitalized calls', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.CAPITALIZED_CALLS]), +}; +export const StaticComponentsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'todo', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.STATIC_COMPONENTS]), +}; + +export const InvalidWritesRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.INVALID_WRITE]), +}; +export const UseMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Surfaces compilation errors related to invalid useMemo usage', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.VALIDATE_MANUAL_MEMO]), +}; + +// TODO: test cases +export const NoDynamicManualMemoRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.DYNAMIC_MANUAL_MEMO]), +}; + +export const UnsafeRefsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to unsafe refs', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.EXHAUSTIVE_DEPS]), +}; + +export const ValidateSetStateInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_RENDER]), +}; + +export const NoSetStateInEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Surfaces compilation errors related to setState in render', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_SET_STATE_IN_EFFECTS]), +}; + +export const NoRefAccessInRenderRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.NO_REF_ACCESS_IN_RENDER]), +}; + +export const NoImpureFunctionCallsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.IMPURE_FUNCTIONS]), +}; + +export const UnnecessaryEffectsRule: Rule.RuleModule = { + meta: { + type: 'problem', + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNNECESSARY_EFFECTS]), +}; + +export const NoAmbiguousJsxRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on JSX usage that is ambiguous.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.JSX_IN_TRY]), +}; + +// TODO: test cases +export const NoUnsupportedSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler does not and will not support.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.UNSUPPORTED_SYNTAX]), +}; + +// TODO: test cases +export const WarnOnTodoSyntaxRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: + 'Warns on JavaScript syntax that the compiler currently does not support, but may in the future.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.TODO_SYNTAX]), +}; + +// TODO: test cases +export const ValidateCompilerConfigRule: Rule.RuleModule = { + meta: { + type: 'problem', + docs: { + description: 'Validates the React Compiler configuration', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create: makeRule([LinterCategory.COMPILER_CONFIG]), +}; + +export const WarnOnUnactionableFailuresRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + description: 'Warns on compilation failures that are not actionable', + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const event of results.events) { + if (event.kind === 'CompileError') { + const detail = event.detail; + if (detail.linterCategory == null) { const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; + if (loc == null || typeof loc === 'symbol') { + continue; } - if ( - hasFlowSuppression(loc, 'react-rule-hook') || - hasFlowSuppression(loc, 'react-rule-unsafe-ref') - ) { - // If Flow already caught this error, we don't need to report it again. - return; - } - if (loc != null) { - context.report({ - message: detail.printErrorMessage(sourceCode.text, { - eslint: true, - }), - loc, - suggest, - }); - } - } - }, - }; - - try { - options.environment = validateEnvironmentConfig( - options.environment ?? {}, - ); - } catch (err: unknown) { - options.logger?.logEvent('', err as LoggerEvent); - } - - function hasFlowSuppression( - nodeLoc: BabelSourceLocation, - suppression: string, - ): boolean { - const comments = sourceCode.getAllComments(); - const flowSuppressionRegex = new RegExp( - '\\$FlowFixMe\\[' + suppression + '\\]', - ); - for (const commentNode of comments) { - if ( - flowSuppressionRegex.test(commentNode.value) && - commentNode.loc!.end.line === nodeLoc.start.line - 1 - ) { - return true; + context.report({ + message: detail.printErrorMessage(results.sourceCode, { + eslint: true, + }), + loc, + suggest: makeSuggestions(detail.options), + }); } } - return false; - } - - let babelAST; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - const {parse: babelParse} = require('@babel/parser'); - babelAST = babelParse(sourceCode.text, { - filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } - } - - if (babelAST != null) { - try { - transformFromAstSync(babelAST, sourceCode.text, { - filename, - highlightCode: false, - retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], - sourceType: 'module', - configFile: false, - babelrc: false, - }); - } catch (err) { - /* errors handled by injected logger */ - } - } - - function reportUnusedOptOutDirective(stmt: Statement) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'Literal' && - typeof stmt.expression.value === 'string' && - OPT_OUT_DIRECTIVES.has(stmt.expression.value) && - stmt.loc != null - ) { - context.report({ - message: `Unused '${stmt.expression.value}' directive`, - loc: stmt.loc, - suggest: [ - { - desc: 'Remove the directive', - fix(fixer) { - return fixer.remove(stmt); - }, - }, - ], - }); - } - } - if (shouldReportUnusedOptOutDirective) { - return { - FunctionDeclaration(fnDecl) { - for (const stmt of fnDecl.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - ArrowFunctionExpression(fnExpr) { - if (fnExpr.body.type === 'BlockStatement') { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - } - }, - FunctionExpression(fnExpr) { - for (const stmt of fnExpr.body.body) { - reportUnusedOptOutDirective(stmt); - } - }, - }; - } else { - return {}; } + return {}; }, }; -export default rule; +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer) { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..655eaf5197 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,323 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {transformFromAstSync, traverse} from '@babel/core'; +import {parse as babelParse} from '@babel/parser'; +import {Directive, File} from '@babel/types'; +// @ts-expect-error: no types available +import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import BabelPluginReactCompiler, { + parsePluginOptions, + validateEnvironmentConfig, + OPT_OUT_DIRECTIVES, + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +// @ts-expect-error: no types available +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +const COMPILER_OPTIONS: Partial = { + noEmit: true, + panicThreshold: 'none', + // Don't emit errors on Flow suppressions--Flow already gave a signal + flowSuppressions: false, + environment: validateEnvironmentConfig({ + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + // TODO: remove, this should be in the type system + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + }), +}; + +export type UnusedOptOutDirective = { + loc: SourceLocation; + range: [number, number]; + directive: string; +}; +export type RunCacheEntry = { + sourceCode: string; + filename: string; + userOpts: PluginOptions; + flowSuppressions: Array<{line: number; code: string}>; + unusedOptOutDirectives: Array; + events: LoggerEvent[]; +}; + +type RunParams = { + sourceCode: SourceCode; + filename: string; + userOpts: PluginOptions; +}; +const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g; + +function getFlowSuppressions( + sourceCode: SourceCode, +): Array<{line: number; code: string}> { + const comments = sourceCode.getAllComments(); + const results: Array<{line: number; code: string}> = []; + + for (const commentNode of comments) { + const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX); + for (const match of matches) { + if (match.index != null && commentNode.loc != null) { + const code = match[1]; + results.push({ + line: commentNode.loc!.end.line, + code, + }); + } + } + } + return results; +} + +function filterUnusedOptOutDirectives( + directives: ReadonlyArray, +): Array { + const results: Array = []; + for (const directive of directives) { + if ( + OPT_OUT_DIRECTIVES.has(directive.value.value) && + directive.loc != null + ) { + results.push({ + loc: directive.loc, + directive: directive.value.value, + range: [directive.start!, directive.end!], + }); + } + } + return results; +} + +function runReactCompilerImpl({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + // Compat with older versions of eslint + for (const [key, entry] of Object.entries(userOpts)) { + if (key === 'environment' && COMPILER_OPTIONS.environment != null) { + for (const envKey of Object.keys(entry as Record)) { + if ( + COMPILER_OPTIONS.environment.hasOwnProperty(envKey) && + isDeepStrictEqual( + (entry as Record)[envKey], + (COMPILER_OPTIONS.environment as Record)[envKey], + ) + ) { + console.warn('Conflicting environment option detected: ' + envKey); + } + } + } else if (COMPILER_OPTIONS.hasOwnProperty(key)) { + if (isDeepStrictEqual(entry, (COMPILER_OPTIONS as any)[key])) { + console.warn('Conflicting option detected: ' + key); + } + } + } + const options: PluginOptions = parsePluginOptions({ + ...COMPILER_OPTIONS, + ...userOpts, + environment: { + ...COMPILER_OPTIONS.environment, + ...userOpts.environment, + }, + }); + const results: RunCacheEntry = { + sourceCode: sourceCode.text, + filename, + userOpts, + flowSuppressions: [], + unusedOptOutDirectives: [], + events: [], + }; + const userLogger: Logger | null = options.logger; + options.logger = { + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); + results.events.push(event); + }, + }; + + try { + options.environment = validateEnvironmentConfig(options.environment ?? {}); + } catch (err: unknown) { + options.logger?.logEvent(filename, err as LoggerEvent); + } + + let babelAST: ParseResult | null = null; + if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch { + /* empty */ + } + } else { + try { + babelAST = HermesParser.parse(sourceCode.text, { + babel: true, + enableExperimentalComponentSyntax: true, + sourceFilename: filename, + sourceType: 'module', + }); + } catch { + /* empty */ + } + } + + if (babelAST != null) { + results.flowSuppressions = getFlowSuppressions(sourceCode); + try { + transformFromAstSync(babelAST, sourceCode.text, { + filename, + highlightCode: false, + retainLines: true, + plugins: [ + [PluginProposalPrivateMethods, {loose: true}], + [BabelPluginReactCompiler, options], + ], + sourceType: 'module', + configFile: false, + babelrc: false, + }); + + if (results.events.filter(e => e.kind === 'CompileError').length === 0) { + traverse(babelAST, { + FunctionDeclaration(path) { + path.node; + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + ArrowFunctionExpression(path) { + if (path.node.body.type === 'BlockStatement') { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + } + }, + FunctionExpression(path) { + results.unusedOptOutDirectives.push( + ...filterUnusedOptOutDirectives(path.node.body.directives), + ); + }, + }); + } + } catch (err) { + /* errors handled by injected logger */ + } + } + + return results; +} + +const SENTINEL = Symbol(); + +type T = {[k: string]: any}; + +// Array backed LRU cache -- should be small < 10 elements +class LRUCache { + // newest at headIdx, then headIdx + 1, ..., tailIdx + #values: Array<[K, T | Error] | [typeof SENTINEL, void]>; + #headIdx: number = 0; + // #store: (key: K) => T | Error; + // #isValue: null | ((key: T | Error) => key is T); + + constructor( + size: number, + // store: (key: K) => T | Error, + // isValue?: (key: T | Error) => key is T, + ) { + this.#values = new Array(size).fill(SENTINEL); + // this.#store = store; + // this.#isValue = isValue ?? null; + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + let idx = this.#values.findIndex(entry => entry[0] === key); + // If found, move to front + if (idx === this.#headIdx) { + return this.#values[this.#headIdx][1] as T; + } else if (idx < 0) { + return null; + // const value = this.#store(key); + // if (this.#isValue && !this.#isValue(value)) { + // return value; // Return error directly + // } + // this.#headIdx = + // (this.#headIdx - 1 + this.#values.length) % this.#values.length; + // this.#values[this.#headIdx] = [key, value]; + } + + const entry: [K, T] = this.#values[idx] as [K, T]; + + const len = this.#values.length; + for (let i = 0; i < Math.min(idx, len - 1); i++) { + this.#values[(this.#headIdx + i + 1) % len] = + this.#values[(this.#headIdx + i) % len]; + } + this.#values[this.#headIdx] = entry; + return entry[1]; + } + push(key: K, value: T): void { + this.#headIdx = + (this.#headIdx - 1 + this.#values.length) % this.#values.length; + this.#values[this.#headIdx] = [key, value]; + } +} +const cache = new LRUCache(10); + +export default function runReactCompiler({ + sourceCode, + filename, + userOpts, +}: RunParams): RunCacheEntry { + const entry = cache.get(filename); + if ( + entry != null && + entry.sourceCode === sourceCode.text && + isDeepStrictEqual(entry.userOpts, userOpts) + ) { + return entry; + } else if (entry != null) { + if (process.env['DEBUG']) { + console.log( + `Cache hit for ${filename}, but source code or options changed, recomputing`, + ); + } + } + + const runEntry = runReactCompilerImpl({ + sourceCode, + filename, + userOpts, + }); + // If we have a cache entry, we can update it + if (entry != null) { + Object.assign(entry, runEntry); + } else { + cache.push(filename, runEntry); + } + return {...runEntry}; +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index a159359773..a6041bd5cc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -338,7 +338,16 @@ export async function transformFixtureInput( if (logs.length !== 0) { formattedLogs = logs .map(({event}) => { - return JSON.stringify(event); + return JSON.stringify(event, (key, value) => { + if ( + key === 'detail' && + value != null && + typeof value.serialize === 'function' + ) { + return value.serialize(); + } + return value; + }); }) .join('\n'); } diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 6e1bc7feeb..696261cbf5 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -542,11 +542,6 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" @@ -1605,7 +1600,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": +"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -1613,14 +1608,6 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2148,6 +2135,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + "@jest/environment@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz" @@ -2178,6 +2170,13 @@ "@types/node" "*" jest-mock "^29.5.0" +"@jest/expect-utils@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.0.5.tgz#9d42e4b8bc80367db30abc6c42b2cb14073f66fc" + integrity sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew== + dependencies: + "@jest/get-type" "30.0.1" + "@jest/expect-utils@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz" @@ -2274,6 +2273,11 @@ jest-mock "^29.5.0" jest-util "^29.5.0" +"@jest/get-type@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.0.1.tgz#0d32f1bbfba511948ad247ab01b9007724fc9f52" + integrity sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw== + "@jest/globals@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz" @@ -2313,6 +2317,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + "@jest/reporters@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz" @@ -2435,6 +2447,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^28.1.3": version "28.1.3" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz" @@ -2663,6 +2682,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^24.9.0": version "24.9.0" resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" @@ -2965,6 +2997,11 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.34.0": + version "0.34.38" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" + integrity sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" @@ -3154,6 +3191,11 @@ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== +"@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/istanbul-lib-report@*": version "3.0.0" resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" @@ -3176,6 +3218,13 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + "@types/jest@^28.1.6": version "28.1.8" resolved "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz" @@ -3200,6 +3249,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/jsdom@^20.0.0": version "20.0.0" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz" @@ -3282,6 +3339,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz" @@ -3309,6 +3371,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz" @@ -3740,7 +3809,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3803,11 +3872,32 @@ aria-query@^5.0.0: resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" @@ -3815,6 +3905,11 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async@^3.2.3: version "3.2.6" resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" @@ -3825,6 +3920,13 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^1.6.1: version "1.7.4" resolved "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz" @@ -4245,7 +4347,7 @@ cac@^6.7.14: resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== @@ -4253,7 +4355,17 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bound@^1.0.2: +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -4295,7 +4407,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4377,6 +4489,11 @@ ci-info@^3.2.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz" integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" @@ -4722,6 +4839,33 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" @@ -4788,6 +4932,24 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + degenerator@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" @@ -4922,7 +5084,7 @@ dreamopt@~0.6.0: dependencies: wordwrap ">=0.0.2" -dunder-proto@^1.0.1: +dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== @@ -5033,7 +5195,67 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-define-property@^1.0.1: +es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.9: + version "1.24.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== @@ -5050,6 +5272,25 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@0.8.x: version "0.8.2" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz" @@ -5428,6 +5669,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.0.5.tgz#c23bf193c5e422a742bfd2990ad990811de41a5a" + integrity sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ== + dependencies: + "@jest/expect-utils" "30.0.5" + "@jest/get-type" "30.0.1" + jest-matcher-utils "30.0.5" + jest-message-util "30.0.5" + jest-mock "30.0.5" + jest-util "30.0.5" + express-rate-limit@^7.5.0: version "7.5.0" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" @@ -5719,6 +5972,13 @@ follow-redirects@^1.15.6: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" @@ -5788,6 +6048,23 @@ function-bind@^1.1.2: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -5798,7 +6075,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== @@ -5819,7 +6096,7 @@ get-package-type@^0.1.0: resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-proto@^1.0.1: +get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -5839,6 +6116,15 @@ get-stream@^6.0.0: resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + get-uri@^6.0.1: version "6.0.4" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz" @@ -5957,6 +6243,14 @@ globals@^14.0.0: resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -5969,12 +6263,12 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.2.0: +gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -5989,6 +6283,11 @@ graphemer@^1.4.0: resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" @@ -5999,11 +6298,32 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.1.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -6231,6 +6551,15 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -6251,6 +6580,15 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -6261,6 +6599,24 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" @@ -6268,6 +6624,19 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz" @@ -6275,11 +6644,35 @@ is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" @@ -6290,6 +6683,16 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + dependencies: + call-bound "^1.0.3" + get-proto "^1.0.0" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -6307,6 +6710,24 @@ is-interactive@^2.0.0: resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -6339,11 +6760,57 @@ is-promise@^4.0.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -6354,11 +6821,36 @@ is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-windows@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -6797,6 +7289,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.0.5.tgz#b40f81e0c0d13e5b81c4d62b0d0dfa6a524ee0fd" + integrity sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + pretty-format "30.0.5" + jest-diff@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz" @@ -7106,6 +7608,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz#dff3334be58faea4a5e1becc228656fbbfc2467d" + integrity sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ== + dependencies: + "@jest/get-type" "30.0.1" + chalk "^4.1.2" + jest-diff "30.0.5" + pretty-format "30.0.5" + jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz" @@ -7146,6 +7658,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.5.tgz#dd12ffec91dd3fa6a59cbd538a513d8e239e070c" + integrity sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz" @@ -7206,6 +7733,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + jest-mock@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz" @@ -7237,6 +7773,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + jest-regex-util@^28.0.2: version "28.0.2" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz" @@ -7683,6 +8224,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + jest-util@^28.0.0, jest-util@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz" @@ -8327,7 +8880,7 @@ merge@^2.1.1: resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -8620,11 +9173,28 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + on-finished@^2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -8695,6 +9265,15 @@ ora@^7.0.1: string-width "^6.1.0" strip-ansi "^7.1.0" +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" @@ -8949,6 +9528,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz" @@ -8975,6 +9559,15 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + pretty-format@^24: version "24.9.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" @@ -9202,7 +9795,7 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -9251,6 +9844,20 @@ readline@^1.3.0: resolved "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz" integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz" @@ -9280,6 +9887,30 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp.escape@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexp.escape/-/regexp.escape-2.0.1.tgz#09e4beef9d202dbd739868f3818223f977cf91da" + integrity sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + for-each "^0.3.3" + safe-regex-test "^1.0.3" + +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz" @@ -9457,6 +10088,17 @@ rxjs@^7.0.0, rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -9467,6 +10109,23 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + safe-stable-stringify@^2.3.1: version "2.5.0" resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" @@ -9576,6 +10235,37 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -9759,6 +10449,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" @@ -9771,6 +10468,14 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + streamx@^2.15.0, streamx@^2.21.0: version "2.22.0" resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz" @@ -9825,6 +10530,38 @@ string-width@^6.1.0: emoji-regex "^10.2.1" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -10232,6 +10969,51 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typed-query-selector@^2.12.0: version "2.12.0" resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz" @@ -10251,6 +11033,16 @@ typescript@^5.4.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" @@ -10460,11 +11252,64 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@^1.2.10, which@^1.2.14: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"