diff --git a/babel.config-ts.js b/babel.config-ts.js index 45efe2a870..02ffb447b3 100644 --- a/babel.config-ts.js +++ b/babel.config-ts.js @@ -8,6 +8,7 @@ module.exports = { '@babel/plugin-syntax-jsx', '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-transform-class-properties', {loose: true}], + ['@babel/plugin-transform-private-methods', {loose: true}], '@babel/plugin-transform-classes', ], presets: [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 2055b25c0c..5940dbb4fd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -47,8 +47,9 @@ export enum ErrorSeverity { } export type CompilerDiagnosticOptions = { + category: ErrorCategory; severity: ErrorSeverity; - category: string; + reason: string; description: string; details: Array; suggestions?: Array | null | undefined; @@ -91,9 +92,10 @@ export type CompilerSuggestion = }; export type CompilerErrorDetailOptions = { + category: ErrorCategory; + severity: ErrorSeverity; reason: string; description?: string | null | undefined; - severity: ErrorSeverity; loc: SourceLocation | null; suggestions?: Array | null | undefined; }; @@ -119,8 +121,8 @@ export class CompilerDiagnostic { return new CompilerDiagnostic({...options, details: []}); } - get category(): CompilerDiagnosticOptions['category'] { - return this.options.category; + get reason(): CompilerDiagnosticOptions['reason'] { + return this.options.reason; } get description(): CompilerDiagnosticOptions['description'] { return this.options.description; @@ -131,6 +133,9 @@ export class CompilerDiagnostic { get suggestions(): CompilerDiagnosticOptions['suggestions'] { return this.options.suggestions; } + get category(): ErrorCategory { + return this.options.category; + } withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic { this.options.details.push(detail); @@ -148,7 +153,7 @@ export class CompilerDiagnostic { printErrorMessage(source: string, options: PrintErrorMessageOptions): string { const buffer = [ - printErrorSummary(this.severity, this.category), + printErrorSummary(this.severity, this.reason), '\n\n', this.description, ]; @@ -193,7 +198,7 @@ export class CompilerDiagnostic { } toString(): string { - const buffer = [printErrorSummary(this.severity, this.category)]; + const buffer = [printErrorSummary(this.severity, this.reason)]; if (this.description != null) { buffer.push(`. ${this.description}.`); } @@ -231,6 +236,9 @@ export class CompilerErrorDetail { get suggestions(): CompilerErrorDetailOptions['suggestions'] { return this.options.suggestions; } + get category(): ErrorCategory { + return this.options.category; + } primaryLocation(): SourceLocation | null { return this.loc; @@ -280,13 +288,14 @@ export class CompilerError extends Error { static invariant( condition: unknown, - options: Omit, + options: Omit, ): asserts condition { if (!condition) { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, + category: ErrorCategory.Invariant, severity: ErrorSeverity.Invariant, }), ); @@ -301,23 +310,28 @@ export class CompilerError extends Error { } static throwTodo( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( - new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}), + new CompilerErrorDetail({ + ...options, + severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, + }), ); throw errors; } static throwInvalidJS( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, }), ); throw errors; @@ -337,13 +351,14 @@ export class CompilerError extends Error { } static throwInvalidConfig( - options: Omit, + options: Omit, ): never { const errors = new CompilerError(); errors.pushErrorDetail( new CompilerErrorDetail({ ...options, severity: ErrorSeverity.InvalidConfig, + category: ErrorCategory.Config, }), ); throw errors; @@ -407,6 +422,7 @@ export class CompilerError extends Error { push(options: CompilerErrorDetailOptions): CompilerErrorDetail { const detail = new CompilerErrorDetail({ + category: options.category, reason: options.reason, description: options.description ?? null, severity: options.severity, @@ -507,3 +523,327 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string { } return `${severityCategory}: ${message}`; } + +/** + * See getRuleForCategory() for how these map to ESLint rules + */ +export enum ErrorCategory { + // Checking for valid hooks usage (non conditional, non-first class, non reactive, etc) + Hooks = 'Hooks', + + // Checking for no capitalized calls (not definitively an error, hence separating) + CapitalizedCalls = 'CapitalizedCalls', + + // Checking for static components + StaticComponents = 'StaticComponents', + + // Checking for valid usage of manual memoization + UseMemo = 'UseMemo', + + // Checks that manual memoization is preserved + PreserveManualMemo = 'PreserveManualMemo', + + // Checking for no mutations of props, hook arguments, hook return values + Immutability = 'Immutability', + + // Checking for assignments to globals + Globals = 'Globals', + + // Checking for valid usage of refs, ie no access during render + Refs = 'Refs', + + // Checks for memoized effect deps + EffectDependencies = 'EffectDependencies', + + // Checks for no setState in effect bodies + EffectSetState = 'EffectSetState', + + EffectDerivationsOfState = 'EffectDerivationsOfState', + + // Validates against try/catch in place of error boundaries + ErrorBoundaries = 'ErrorBoundaries', + + // Checking for pure functions + Purity = 'Purity', + + // Validates against setState in render + RenderSetState = 'RenderSetState', + + // Internal invariants + Invariant = 'Invariant', + + // Todos + Todo = 'Todo', + + // Syntax errors + Syntax = 'Syntax', + + // Checks for use of unsupported syntax + UnsupportedSyntax = 'UnsupportedSyntax', + + // Config errors + Config = 'Config', + + // Gating error + Gating = 'Gating', + + // Suppressions + Suppression = 'Suppression', + + // Issues with auto deps + AutomaticEffectDependencies = 'AutomaticEffectDependencies', + + // Issues with `fire` + Fire = 'Fire', + + // fbt-specific issues + FBT = 'FBT', +} + +export type LintRule = { + // Stores the category the rule corresponds to, used to filter errors when reporting + category: ErrorCategory; + + /** + * The "name" of the rule as it will be used by developers to enable/disable, eg + * "eslint-disable-nest line " + */ + name: string; + + /** + * A description of the rule that appears somewhere in ESLint. This does not affect + * how error messages are formatted + */ + description: string; + + /** + * If true, this rule will automatically appear in the default, "recommended" ESLint + * rule set. Otherwise it will be part of an `allRules` export that developers can + * use to opt-in to showing output of all possible rules. + * + * NOTE: not all validations are enabled by default! Setting this flag only affects + * whether a given rule is part of the recommended set. The corresponding validation + * also should be enabled by default if you want the error to actually show up! + */ + recommended: boolean; +}; + +export function getRuleForCategory(category: ErrorCategory): LintRule { + switch (category) { + case ErrorCategory.AutomaticEffectDependencies: { + return { + category, + name: 'automatic-effect-dependencies', + description: + 'Verifies that automatic effect dependencies are compiled if opted-in', + recommended: true, + }; + } + case ErrorCategory.CapitalizedCalls: { + return { + category, + name: 'capitalized-calls', + description: + 'Validates against calling capitalized functions/methods instead of using JSX', + recommended: false, + }; + } + case ErrorCategory.Config: { + return { + category, + name: 'config', + description: 'Validates the configuration', + recommended: true, + }; + } + case ErrorCategory.EffectDependencies: { + return { + category, + name: 'memoized-effect-dependencies', + description: 'Validates that effect dependencies are memoized', + recommended: false, + }; + } + case ErrorCategory.EffectDerivationsOfState: { + return { + category, + name: 'no-deriving-state-in-effects', + description: + 'Validates against deriving values from state in an effect', + recommended: false, + }; + } + case ErrorCategory.EffectSetState: { + return { + category, + name: 'set-state-in-effect', + description: + 'Validates against calling setState synchronously in an effect', + recommended: true, + }; + } + case ErrorCategory.ErrorBoundaries: { + return { + category, + name: 'error-boundaries', + description: + 'Validates usage of error boundaries instead of try/catch for errors in JSX', + recommended: true, + }; + } + case ErrorCategory.FBT: { + return { + category, + name: 'fbt', + description: 'Validates usage of fbt', + recommended: false, + }; + } + case ErrorCategory.Fire: { + return { + category, + name: 'fire', + description: 'Validates usage of `fire`', + recommended: false, + }; + } + case ErrorCategory.Gating: { + return { + category, + name: 'gating', + description: 'Validates configuration of gating mode', + recommended: true, + }; + } + case ErrorCategory.Globals: { + return { + category, + name: 'globals', + description: + 'Validates against assignment/mutation of globals during render', + recommended: true, + }; + } + case ErrorCategory.Hooks: { + return { + category, + name: 'hooks', + description: 'Validates the rules of hooks', + /** + * TODO: the "Hooks" rule largely reimplements the "rules-of-hooks" non-compiler rule. + * We need to dedeupe these (moving the remaining bits into the compiler) and then enable + * this rule. + */ + recommended: false, + }; + } + case ErrorCategory.Immutability: { + return { + category, + name: 'immutability', + description: + 'Validates that immutable values (props, state, etc) are not mutated', + recommended: true, + }; + } + case ErrorCategory.Invariant: { + return { + category, + name: 'invariant', + description: 'Internal invariants', + recommended: false, + }; + } + case ErrorCategory.PreserveManualMemo: { + return { + category, + name: 'preserve-manual-memoization', + description: + 'Validates that existing manual memoized is preserved by the compiler', + recommended: true, + }; + } + case ErrorCategory.Purity: { + return { + category, + name: 'purity', + description: + 'Validates that the component/hook is pure, and does not call known-impure functions', + recommended: true, + }; + } + case ErrorCategory.Refs: { + return { + category, + name: 'refs', + description: + 'Validates correct usage of refs, not reading/writing during render', + recommended: true, + }; + } + case ErrorCategory.RenderSetState: { + return { + category, + name: 'set-state-in-render', + description: 'Validates against setting state during render', + recommended: true, + }; + } + case ErrorCategory.StaticComponents: { + return { + category, + name: 'static-components', + description: + 'Validates that components are static, not recreated every render', + recommended: true, + }; + } + case ErrorCategory.Suppression: { + return { + category, + name: 'rule-suppression', + description: 'Validates against suppression of other rules', + recommended: false, + }; + } + case ErrorCategory.Syntax: { + return { + category, + name: 'syntax', + description: 'Validates against invalid syntax', + recommended: false, + }; + } + case ErrorCategory.Todo: { + return { + category, + name: 'todo', + description: 'Unimplemented features', + recommended: false, + }; + } + case ErrorCategory.UnsupportedSyntax: { + return { + category, + name: 'unsupported-syntax', + description: 'Validates against syntax that we do not plan to support', + recommended: true, + }; + } + case ErrorCategory.UseMemo: { + return { + category, + name: 'use-memo', + description: 'Validates usage of the useMemo() hook', + recommended: true, + }; + } + default: { + assertExhaustive(category, `Unsupported category ${category}`); + } + } +} + +export const LintRules: Array = Object.keys(ErrorCategory).map( + category => getRuleForCategory(category as any), +); 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..9653c49576 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -9,7 +9,7 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; import {Scope as BabelScope} from '@babel/traverse'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import { EnvironmentConfig, GeneratedSource, @@ -38,6 +38,7 @@ export function validateRestrictedImports( ImportDeclaration(importDeclPath) { if (restrictedImports.has(importDeclPath.node.source.value)) { error.push({ + category: ErrorCategory.Todo, severity: ErrorSeverity.Todo, reason: 'Bailing out due to blocklisted import', description: `Import from module ${importDeclPath.node.source.value}`, @@ -205,6 +206,7 @@ export class ProgramContext { } const error = new CompilerError(); error.push({ + category: ErrorCategory.Todo, severity: ErrorSeverity.Todo, reason: 'Encountered conflicting global in generated program', description: `Conflict from local binding ${name}`, 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..9215150162 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -10,6 +10,7 @@ import * as t from '@babel/types'; import { CompilerError, CompilerErrorDetail, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {ExternalFunction, ReactFunctionType} from '../HIR/Environment'; @@ -105,6 +106,7 @@ function findDirectivesDynamicGating( reason: `Dynamic gating directive is not a valid JavaScript identifier`, description: `Found '${directive.value.value}'`, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Gating, loc: directive.loc ?? null, suggestions: null, }); @@ -121,6 +123,7 @@ function findDirectivesDynamicGating( .map(r => r.directive.value.value) .join(', ')}]`, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Gating, loc: result[0].directive.loc ?? null, suggestions: null, }); @@ -456,6 +459,7 @@ export function compileProgram( reason: 'Unexpected compiled functions when module scope opt-out is present', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: null, }), ); @@ -811,6 +815,7 @@ function shouldSkipCompilation( description: "When the 'sources' config options is specified, the React compiler will only compile files with a name", severity: ErrorSeverity.InvalidConfig, + category: ErrorCategory.Config, loc: null, }), ); 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..a0d06f96f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -11,6 +11,7 @@ import { CompilerDiagnostic, CompilerError, CompilerSuggestionOperation, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; @@ -183,9 +184,10 @@ export function suppressionsToCompilerError( } error.pushDiagnostic( CompilerDiagnostic.create({ - category: reason, + reason: 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, + category: ErrorCategory.Suppression, 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..beaaff0f79 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -13,7 +13,11 @@ 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, + CompilerDiagnosticOptions, + ErrorCategory, +} from '../CompilerError'; function throwInvalidReact( options: Omit, @@ -92,7 +96,8 @@ function assertValidEffectImportReference( */ throwInvalidReact( { - category: + category: ErrorCategory.AutomaticEffectDependencies, + reason: 'Cannot infer dependencies of this effect. This will break your build!', description: 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + @@ -123,8 +128,8 @@ function assertValidFireImportReference( ); throwInvalidReact( { - category: - '[Fire] Untransformed reference to compiler-required feature.', + category: ErrorCategory.Fire, + reason: '[Fire] Untransformed reference to compiler-required feature.', description: 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + maybeErrorDiagnostic 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..77f2a04e7c 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, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {Err, Ok, Result} from '../Utils/Result'; @@ -108,7 +109,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.Invariant, - category: 'Could not find binding', + category: ErrorCategory.Invariant, + reason: 'Could not find binding', description: `[BuildHIR] Could not find binding for param \`${param.node.name}\`.`, }).withDetail({ kind: 'error', @@ -172,7 +174,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.Todo, - category: `Handle ${param.node.type} parameters`, + category: ErrorCategory.Todo, + reason: `Handle ${param.node.type} parameters`, description: `[BuildHIR] Add support for ${param.node.type} parameters.`, }).withDetail({ kind: 'error', @@ -203,7 +206,8 @@ export function lower( builder.errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidJS, - category: `Unexpected function body kind`, + category: ErrorCategory.Syntax, + reason: `Unexpected function body kind`, description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`, }).withDetail({ kind: 'error', @@ -273,6 +277,7 @@ function lowerStatement( reason: '(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -460,6 +465,7 @@ function lowerStatement( } else if (!binding.path.isVariableDeclarator()) { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Unsupported declaration type for hoisting', description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`, suggestions: null, @@ -469,6 +475,7 @@ function lowerStatement( } else { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Handle non-const declarations for hoisting', description: `variable "${binding.identifier.name}" declared with ${binding.kind}`, suggestions: null, @@ -549,6 +556,7 @@ function lowerStatement( reason: '(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -621,6 +629,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -772,6 +781,7 @@ function lowerStatement( builder.errors.push({ reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: case_.node.loc ?? null, suggestions: null, }); @@ -844,6 +854,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -872,6 +883,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: id.node.loc ?? null, suggestions: null, }); @@ -889,6 +901,7 @@ function lowerStatement( builder.errors.push({ reason: `Expect \`const\` declaration not to be reassigned`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: id.node.loc ?? null, suggestions: [ { @@ -936,6 +949,7 @@ function lowerStatement( reason: `Expected variable declaration to be an identifier if no initializer was provided`, description: `Got a \`${id.type}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1044,6 +1058,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle for-await loops`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1276,6 +1291,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1285,6 +1301,7 @@ function lowerStatement( builder.errors.push({ reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.node.loc ?? null, suggestions: null, }); @@ -1378,6 +1395,7 @@ function lowerStatement( reason: `JavaScript 'with' syntax is not supported`, description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`, severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1398,6 +1416,7 @@ function lowerStatement( reason: 'Inline `class` declarations are not supported', description: `Move class declarations outside of components/hooks`, severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1427,6 +1446,7 @@ function lowerStatement( reason: 'JavaScript `import` and `export` statements may only appear at the top level of a module', severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1442,6 +1462,7 @@ function lowerStatement( reason: 'TypeScript `namespace` statements may only appear at the top level of a module', severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: stmtPath.node.loc ?? null, suggestions: null, }); @@ -1520,6 +1541,7 @@ function lowerObjectPropertyKey( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: key.node.loc ?? null, suggestions: null, }); @@ -1545,6 +1567,7 @@ function lowerObjectPropertyKey( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: key.node.loc ?? null, suggestions: null, }); @@ -1602,6 +1625,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valuePath.node.loc ?? null, suggestions: null, }); @@ -1628,6 +1652,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyPath.node.loc ?? null, suggestions: null, }); @@ -1649,6 +1674,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyPath.node.loc ?? null, suggestions: null, }); @@ -1682,6 +1708,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: element.node.loc ?? null, suggestions: null, }); @@ -1702,6 +1729,7 @@ function lowerExpression( reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`, description: `Got a \`${calleePath.node.type}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: calleePath.node.loc ?? null, suggestions: null, }); @@ -1728,6 +1756,7 @@ function lowerExpression( builder.errors.push({ reason: `Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported). This error is likely caused by a bug in React Compiler. Please file an issue`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: calleePath.node.loc ?? null, suggestions: null, }); @@ -1762,6 +1791,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: leftPath.node.loc ?? null, suggestions: null, }); @@ -1774,6 +1804,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Pipe operator not supported`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: leftPath.node.loc ?? null, suggestions: null, }); @@ -1803,6 +1834,7 @@ function lowerExpression( builder.errors.push({ reason: `Expected sequence expression to have at least one expression`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2015,6 +2047,7 @@ function lowerExpression( reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`, description: `Expected an LVal, got: ${left.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: left.node.loc ?? null, suggestions: null, }); @@ -2043,6 +2076,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2142,6 +2176,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -2181,6 +2216,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: attribute.node.loc ?? null, suggestions: null, }); @@ -2194,6 +2230,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: namePath.node.loc ?? null, suggestions: null, }); @@ -2224,6 +2261,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valueExpr.node?.loc ?? null, suggestions: null, }); @@ -2234,6 +2272,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: valueExpr.node.loc ?? null, suggestions: null, }); @@ -2291,7 +2330,8 @@ function lowerExpression( if (locations.length > 1) { CompilerError.throwDiagnostic({ severity: ErrorSeverity.Todo, - category: 'Support duplicate fbt tags', + category: ErrorCategory.FBT, + reason: 'Support duplicate fbt tags', description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, details: locations.map(loc => { return { @@ -2352,6 +2392,7 @@ function lowerExpression( reason: '(BuildHIR::lowerExpression) Handle tagged template with interpolations', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2370,6 +2411,7 @@ function lowerExpression( reason: '(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2392,6 +2434,7 @@ function lowerExpression( builder.errors.push({ reason: `Unexpected quasi and subexpression lengths in template literal`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2402,6 +2445,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2444,6 +2488,7 @@ function lowerExpression( builder.errors.push({ reason: `Only object properties can be deleted`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: [ { @@ -2459,6 +2504,7 @@ function lowerExpression( builder.errors.push({ reason: `Throw expressions are not supported`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: expr.node.loc ?? null, suggestions: [ { @@ -2580,6 +2626,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2588,6 +2635,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2608,6 +2656,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: exprLoc, suggestions: null, }); @@ -2617,6 +2666,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprLoc, suggestions: null, }); @@ -2672,6 +2722,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2681,6 +2732,7 @@ function lowerExpression( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -2978,6 +3030,7 @@ function lowerReorderableExpression( builder.errors.push({ reason: `(BuildHIR::node.lowerReorderableExpression) Expression type \`${expr.type}\` cannot be safely reordered`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: expr.node.loc ?? null, suggestions: null, }); @@ -3174,6 +3227,7 @@ function lowerArguments( builder.errors.push({ reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: argPath.node.loc ?? null, suggestions: null, }); @@ -3209,6 +3263,7 @@ function lowerMemberExpression( builder.errors.push({ reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyNode.node.loc ?? null, suggestions: null, }); @@ -3230,6 +3285,7 @@ function lowerMemberExpression( builder.errors.push({ reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: propertyNode.node.loc ?? null, suggestions: null, }); @@ -3289,6 +3345,7 @@ function lowerJsxElementName( reason: `Expected JSXNamespacedName to have no colons in the namespace or name`, description: `Got \`${namespace}\` : \`${name}\``, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3303,6 +3360,7 @@ function lowerJsxElementName( builder.errors.push({ reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3401,6 +3459,7 @@ function lowerJsxElement( builder.errors.push({ reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3588,6 +3647,7 @@ function lowerIdentifier( description: 'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler', severity: ErrorSeverity.UnsupportedJS, + category: ErrorCategory.UnsupportedSyntax, loc: exprPath.node.loc ?? null, suggestions: null, }); @@ -3644,6 +3704,7 @@ function lowerIdentifierForAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, loc: path.node.loc ?? null, suggestions: null, }); @@ -3656,6 +3717,7 @@ function lowerIdentifierForAssignment( builder.errors.push({ reason: `Cannot reassign a \`const\` variable`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: path.node.loc ?? null, description: binding.identifier.name != null @@ -3713,6 +3775,7 @@ function lowerAssignment( builder.errors.push({ reason: `Expected \`const\` declaration not to be reassigned`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: lvalue.node.loc ?? null, suggestions: null, }); @@ -3727,6 +3790,7 @@ function lowerAssignment( builder.errors.push({ reason: `Unexpected context variable kind`, severity: ErrorSeverity.InvalidJS, + category: ErrorCategory.Syntax, loc: lvalue.node.loc ?? null, suggestions: null, }); @@ -3798,6 +3862,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -3810,6 +3875,7 @@ function lowerAssignment( reason: '(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property', severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -3875,6 +3941,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -3914,6 +3981,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -3987,6 +4055,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: argument.node.loc ?? null, suggestions: null, }); @@ -4018,6 +4087,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: property.node.loc ?? GeneratedSource, @@ -4035,6 +4105,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -4044,6 +4115,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: property.node.loc ?? null, suggestions: null, }); @@ -4058,6 +4130,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: element.node.loc ?? null, suggestions: null, }); @@ -4080,6 +4153,7 @@ function lowerAssignment( } else if (identifier.kind === 'Global') { builder.errors.push({ severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, reason: 'Expected reassignment of globals to enable forceTemporaries', loc: element.node.loc ?? GeneratedSource, @@ -4229,6 +4303,7 @@ function lowerAssignment( builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: lvaluePath.node.loc ?? null, suggestions: null, }); 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..272b5fc075 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 {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -310,7 +310,8 @@ export default class HIRBuilder { if (node.name === 'fbt') { CompilerError.throwDiagnostic({ severity: ErrorSeverity.Todo, - category: 'Support local variables named `fbt`', + category: ErrorCategory.FBT, + reason: 'Support local variables named `fbt`', description: 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', details: [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 34ce7f7694..7d2b0b234c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -986,13 +986,13 @@ export function printAliasingEffect(effect: AliasingEffect): string { return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`; } case 'MutateFrozen': { - return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'MutateGlobal': { - return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Impure': { - return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; + return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; } case 'Render': { return `Render ${printPlaceForAliasEffect(effect.place)}`; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts index 80694a7a78..7f30e25a5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts @@ -231,7 +231,7 @@ export function hashEffect(effect: AliasingEffect): string { effect.kind, effect.place.identifier.id, effect.error.severity, - effect.error.category, + effect.error.reason, effect.error.description, printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource), ].join(':'); 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..412efcfe7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -11,6 +11,7 @@ import { ErrorSeverity, SourceLocation, } from '..'; +import {ErrorCategory} from '../CompilerError'; import { CallExpression, Effect, @@ -300,8 +301,9 @@ function extractManualMemoizationArgs( if (fnPlace == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected a callback function to be passed to ${kind}`, + reason: `Expected a callback function to be passed to ${kind}`, description: `Expected a callback function to be passed to ${kind}`, suggestions: null, }).withDetail({ @@ -315,8 +317,9 @@ function extractManualMemoizationArgs( if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Unexpected spread argument to ${kind}`, + reason: `Unexpected spread argument to ${kind}`, description: `Unexpected spread argument to ${kind}`, suggestions: null, }).withDetail({ @@ -335,8 +338,9 @@ function extractManualMemoizationArgs( if (maybeDepsList == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list for ${kind} to be an array literal`, + reason: `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({ @@ -353,8 +357,9 @@ function extractManualMemoizationArgs( if (maybeDep == null) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, + reason: `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({ @@ -459,7 +464,8 @@ export function dropManualMemoization( errors.pushDiagnostic( CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks must return a value', + category: ErrorCategory.UseMemo, + reason: 'useMemo() callbacks must return a value', description: `This ${ manualMemo.loadInstr.value.kind === 'PropertyLoad' ? 'React.useMemo' @@ -498,8 +504,9 @@ export function dropManualMemoization( if (!sidemap.functions.has(fnPlace.identifier.id)) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: `Expected the first argument to be an inline function expression`, + reason: `Expected the first argument to be an inline function expression`, description: `Expected the first argument to be an inline function expression`, suggestions: [], }).withDetail({ 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 d051fb3725..36f32213ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -69,6 +69,7 @@ import { hashEffect, MutationReason, } from './AliasingEffects'; +import {ErrorCategory} from '../CompilerError'; const DEBUG = false; @@ -452,8 +453,9 @@ function applySignature( ? `\`${effect.value.identifier.name.value}\`` : 'value'; const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', + reason: 'This value cannot be modified', description: `${reason}.`, }).withDetail({ kind: 'error', @@ -1036,8 +1038,9 @@ function applyEffect( effect.value.identifier.declarationId, ); const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access variable before it is declared', + reason: '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.`, }); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { @@ -1075,8 +1078,9 @@ function applyEffect( ? `\`${effect.value.identifier.name.value}\`` : 'value'; const diagnostic = CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'This value cannot be modified', + reason: 'This value cannot be modified', description: `${reason}.`, }).withDetail({ kind: 'error', @@ -2033,8 +2037,9 @@ function computeSignatureForInstruction( kind: 'MutateGlobal', place: value.value, error: CompilerDiagnostic.create({ + category: ErrorCategory.Globals, severity: ErrorSeverity.InvalidReact, - category: + reason: '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({ @@ -2132,8 +2137,9 @@ function computeEffectsForLegacySignature( kind: 'Impure', place: receiver, error: CompilerDiagnostic.create({ + category: ErrorCategory.Purity, severity: ErrorSeverity.InvalidReact, - category: 'Cannot call impure function during render', + reason: 'Cannot call impure function during render', description: (signature.canonicalName != null ? `\`${signature.canonicalName}\` is an impure function. ` diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts index 91e2ce0692..3588cf32f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -42,6 +42,7 @@ import { mapInstructionValueOperands, mapTerminalOperands, } from '../HIR/visitors'; +import {ErrorCategory} from '../CompilerError'; type InlinedJsxDeclarationMap = Map< DeclarationId, @@ -83,6 +84,7 @@ export function inlineJsxTransform( kind: 'CompileDiagnostic', fnLoc: null, detail: { + category: ErrorCategory.Todo, reason: 'JSX Inlining is not supported on value blocks', loc: instr.loc, }, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index f7da522954..c02a41f8f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -13,7 +13,7 @@ import { pruneUnusedLabels, renameVariables, } from '.'; -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError'; import {Environment, ExternalFunction} from '../HIR'; import { ArrayPattern, @@ -2185,6 +2185,7 @@ function codegenInstructionValue( (declarator.id as t.Identifier).name }'`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: declarator.loc ?? null, suggestions: null, }); @@ -2193,6 +2194,7 @@ function codegenInstructionValue( cx.errors.push({ reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`, severity: ErrorSeverity.Todo, + category: ErrorCategory.Todo, loc: stmt.loc ?? null, suggestions: null, }); 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..393f95d94c 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 {ErrorCategory} from '../CompilerError'; /* * TODO(jmbrown): @@ -133,6 +134,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description: null, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, reason: '[InsertFire] No LoadGlobal found for useEffect call', suggestions: null, }); @@ -179,6 +181,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -189,6 +192,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { description: 'You must use an array literal for an effect dependency array when that effect uses `fire()`', severity: ErrorSeverity.Invariant, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -223,6 +227,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description: null, severity: ErrorSeverity.Invariant, + category: ErrorCategory.Invariant, reason: '[InsertFire] No loadLocal found for fire call argument', suggestions: null, @@ -246,6 +251,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { 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, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -264,6 +270,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { loc: value.loc, description, severity: ErrorSeverity.InvalidReact, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -395,6 +402,7 @@ function ensureNoRemainingCalleeCaptures( 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, + category: ErrorCategory.Fire, reason: CANNOT_COMPILE_FIRE, suggestions: null, }); @@ -411,6 +419,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { context.pushError({ loc: place.identifier.loc, description: 'Cannot use `fire` outside of a useEffect function', + category: ErrorCategory.Fire, severity: ErrorSeverity.Invariant, reason: CANNOT_COMPILE_FIRE, suggestions: null, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts index 85fb3922d6..32e3ec82c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/Result.ts @@ -90,10 +90,13 @@ export function Ok(val: T): OkImpl { } class OkImpl implements Result { - constructor(private val: T) {} + #val: T; + constructor(val: T) { + this.#val = val; + } map(fn: (val: T) => U): Result { - return new OkImpl(fn(this.val)); + return new OkImpl(fn(this.#val)); } mapErr(_fn: (val: never) => F): Result { @@ -101,15 +104,15 @@ class OkImpl implements Result { } mapOr(_fallback: U, fn: (val: T) => U): U { - return fn(this.val); + return fn(this.#val); } mapOrElse(_fallback: () => U, fn: (val: T) => U): U { - return fn(this.val); + return fn(this.#val); } andThen(fn: (val: T) => Result): Result { - return fn(this.val); + return fn(this.#val); } and(res: Result): Result { @@ -133,30 +136,30 @@ class OkImpl implements Result { } expect(_msg: string): T { - return this.val; + return this.#val; } expectErr(msg: string): never { - throw new Error(`${msg}: ${this.val}`); + throw new Error(`${msg}: ${this.#val}`); } unwrap(): T { - return this.val; + return this.#val; } unwrapOr(_fallback: T): T { - return this.val; + return this.#val; } unwrapOrElse(_fallback: (val: never) => T): T { - return this.val; + return this.#val; } unwrapErr(): never { - if (this.val instanceof Error) { - throw this.val; + if (this.#val instanceof Error) { + throw this.#val; } - throw new Error(`Can't unwrap \`Ok\` to \`Err\`: ${this.val}`); + throw new Error(`Can't unwrap \`Ok\` to \`Err\`: ${this.#val}`); } } @@ -165,14 +168,17 @@ export function Err(val: E): ErrImpl { } class ErrImpl implements Result { - constructor(private val: E) {} + #val: E; + constructor(val: E) { + this.#val = val; + } map(_fn: (val: never) => U): Result { return this; } mapErr(fn: (val: E) => F): Result { - return new ErrImpl(fn(this.val)); + return new ErrImpl(fn(this.#val)); } mapOr(fallback: U, _fn: (val: never) => U): U { @@ -196,7 +202,7 @@ class ErrImpl implements Result { } orElse(fn: (val: E) => ErrImpl): Result { - return fn(this.val); + return fn(this.#val); } isOk(): this is OkImpl { @@ -208,18 +214,18 @@ class ErrImpl implements Result { } expect(msg: string): never { - throw new Error(`${msg}: ${this.val}`); + throw new Error(`${msg}: ${this.#val}`); } expectErr(_msg: string): E { - return this.val; + return this.#val; } unwrap(): never { - if (this.val instanceof Error) { - throw this.val; + if (this.#val instanceof Error) { + throw this.#val; } - throw new Error(`Can't unwrap \`Err\` to \`Ok\`: ${this.val}`); + throw new Error(`Can't unwrap \`Err\` to \`Ok\`: ${this.#val}`); } unwrapOr(fallback: T): T { @@ -227,10 +233,10 @@ class ErrImpl implements Result { } unwrapOrElse(fallback: (val: E) => T): T { - return fallback(this.val); + return fallback(this.#val); } unwrapErr(): E { - return this.val; + return this.#val; } } 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..af59615525 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -9,6 +9,7 @@ import * as t from '@babel/types'; import { CompilerError, CompilerErrorDetail, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; @@ -124,6 +125,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, description: null, reason, loc: place.loc, @@ -140,6 +142,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, 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', @@ -157,6 +160,7 @@ export function validateHooksUsage( recordError( place.loc, new CompilerErrorDetail({ + category: ErrorCategory.Hooks, 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', @@ -424,7 +428,7 @@ export function validateHooksUsage( } for (const [, error] of errorsByPlace) { - errors.push(error); + errors.pushErrorDetail(error); } return errors.asResult(); } @@ -448,6 +452,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { if (hookKind != null) { errors.pushErrorDetail( new CompilerErrorDetail({ + category: ErrorCategory.Hooks, 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)', 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 163d645957..e1ed71049d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction, IdentifierId, Place} from '../HIR'; import { eachInstructionLValue, @@ -36,8 +37,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void { : 'variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable after render completes', + reason: 'Cannot reassign variable after render completes', description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`, }).withDetail({ kind: 'error', @@ -91,8 +93,9 @@ function getContextReassignment( : 'variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot reassign variable in async function', + reason: 'Cannot reassign variable in async function', 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..186641c3f2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts @@ -6,6 +6,7 @@ */ import {CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import { Identifier, Instruction, @@ -108,6 +109,7 @@ class Visitor extends ReactiveFunctionVisitor { isUnmemoized(deps.identifier, this.scopes)) ) { state.push({ + category: ErrorCategory.EffectDependencies, 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, 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..d0cf41a13c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts @@ -6,6 +6,7 @@ */ import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction, IdentifierId} from '../HIR'; import {DEFAULT_GLOBALS} from '../HIR/Globals'; import {Result} from '../Utils/Result'; @@ -56,6 +57,7 @@ export function validateNoCapitalizedCalls( const calleeName = capitalLoadGlobals.get(calleeIdentifier); if (calleeName != null) { CompilerError.throwInvalidReact({ + category: ErrorCategory.CapitalizedCalls, reason, description: `${calleeName} may be a component.`, loc: value.loc, @@ -79,6 +81,7 @@ export function validateNoCapitalizedCalls( const propertyName = capitalizedProperties.get(propertyIdentifier); if (propertyName != null) { errors.push({ + category: ErrorCategory.CapitalizedCalls, severity: ErrorSeverity.InvalidReact, reason, description: `${propertyName} may be a component.`, 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..f1fa5aec40 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts @@ -6,6 +6,7 @@ */ import {CompilerError, ErrorSeverity, SourceLocation} from '..'; +import {ErrorCategory} from '../CompilerError'; import { ArrayExpression, BlockId, @@ -219,6 +220,7 @@ function validateEffect( for (const loc of setStateLocations) { errors.push({ + category: ErrorCategory.EffectDerivationsOfState, 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, 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 f3543e113a..f49a9a0a47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import { HIRFunction, IdentifierId, @@ -64,8 +65,9 @@ export function validateNoFreezingKnownMutableFunctions( : 'a local variable'; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Immutability, severity: ErrorSeverity.InvalidReact, - category: 'Cannot modify local variables after render completes', + reason: 'Cannot modify local variables after render completes', 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 e7f8b1c0e5..868352e050 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {HIRFunction} from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects'; import {Result} from '../Utils/Result'; @@ -36,7 +37,8 @@ export function validateNoImpureFunctionsInRender( if (signature != null && signature.impure === true) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: 'Cannot call impure function during render', + category: ErrorCategory.Purity, + reason: 'Cannot call impure function during render', description: (signature.canonicalName != null ? `\`${signature.canonicalName}\` 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..158935f9e6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -6,6 +6,7 @@ */ import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..'; +import {ErrorCategory} from '../CompilerError'; import {BlockId, HIRFunction} from '../HIR'; import {Result} from '../Utils/Result'; import {retainWhere} from '../Utils/utils'; @@ -36,8 +37,9 @@ export function validateNoJSXInTryStatement( case 'JsxFragment': { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.ErrorBoundaries, severity: ErrorSeverity.InvalidReact, - category: 'Avoid constructing JSX within try/catch', + 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)`, }).withDetail({ kind: 'error', 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..679d355e32 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -468,8 +469,9 @@ function validateNoRefAccessInRenderImpl( didError = true; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -731,8 +733,9 @@ function guardCheck(errors: CompilerError, operand: Place, env: Env): void { if (env.get(operand.identifier.id)?.kind === 'Guard') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -755,8 +758,9 @@ function validateNoRefValueAccess( ) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -781,8 +785,9 @@ function validateNoRefPassedToFunction( ) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -803,8 +808,9 @@ function validateNoRefUpdate( if (type?.kind === 'Ref' || type?.kind === 'RefValue') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', @@ -824,8 +830,9 @@ function validateNoDirectRefValueAccess( if (type?.kind === 'RefValue') { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.Refs, severity: ErrorSeverity.InvalidReact, - category: 'Cannot access refs during render', + reason: 'Cannot access refs during render', description: ERROR_DESCRIPTION, }).withDetail({ kind: 'error', 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..4a24098a9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -96,7 +97,8 @@ export function validateNoSetStateInEffects( if (setState !== undefined) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.EffectSetState, + 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. ' + 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..2ee9ab64b7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {HIRFunction, IdentifierId, isSetStateType} from '../HIR'; @@ -128,7 +129,8 @@ function validateNoSetStateInRenderImpl( if (activeManualMemoId !== null) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.RenderSetState, + 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)', @@ -143,7 +145,8 @@ function validateNoSetStateInRenderImpl( } else if (unconditionalBlocks.has(block.id)) { errors.pushDiagnostic( CompilerDiagnostic.create({ - category: + category: ErrorCategory.RenderSetState, + 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)', @@ -152,7 +155,7 @@ function validateNoSetStateInRenderImpl( }).withDetail({ kind: 'error', loc: callee.loc, - message: 'Found setState() within useMemo()', + message: 'Found setState() in render', }), ); } 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..516ca232e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import { @@ -281,8 +282,9 @@ function validateInferredDep( } errorState.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + 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. ', @@ -535,8 +537,9 @@ class Visitor extends ReactiveFunctionVisitor { ) { state.errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + 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. ', @@ -583,8 +586,9 @@ class Visitor extends ReactiveFunctionVisitor { if (isUnmemoized(identifier, this.scopes)) { state.errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.PreserveManualMemo, severity: ErrorSeverity.CannotPreserveMemoization, - category: + 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. ', 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..375b8e8f01 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {HIRFunction, IdentifierId, SourceLocation} from '../HIR'; @@ -65,8 +66,9 @@ export function validateStaticComponents( if (location != null) { error.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.StaticComponents, severity: ErrorSeverity.InvalidReact, - category: 'Cannot create components during render', + reason: 'Cannot create components during render', 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..3146bbea38 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -8,6 +8,7 @@ import { CompilerDiagnostic, CompilerError, + ErrorCategory, ErrorSeverity, } from '../CompilerError'; import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR'; @@ -74,8 +75,9 @@ export function validateUseMemo(fn: HIRFunction): Result { : firstParam.place.loc; errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: 'useMemo() callbacks may not accept parameters', + 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.', suggestions: null, @@ -90,8 +92,9 @@ export function validateUseMemo(fn: HIRFunction): Result { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { errors.pushDiagnostic( CompilerDiagnostic.create({ + category: ErrorCategory.UseMemo, severity: ErrorSeverity.InvalidReact, - category: + reason: 'useMemo() callbacks may not be async or generator functions', description: 'useMemo() callbacks are called once and must synchronously return a value.', 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..c4adc4c9bf 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() in render 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() in render 8 | 9 | return x; 10 | } 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..3a5990e162 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() in render 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..c2beb3424a 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() in render 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..faef4dd599 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() in render 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..9d9c741f7a 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() in render 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..07dd0b0a17 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() in render 17 | 18 | return [x]; 19 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md index 42d988804b..58e85caf84 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"severity":"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. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","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. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}} ``` ### Eval output 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..7f8f1d4331 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":{"category":"Gating","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"}}}} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md index d3a6e5f0d7..14bd646bb1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d3b746b1f8..2f930964ac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","severity":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 1ecf4778d7..9a8a2a78e0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` 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..7ecd65382c 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,7 +54,7 @@ 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","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"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":"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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 6583f0a4e2..5c7c4360e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -65,7 +65,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"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)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","severity":"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)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index b6e9e87ada..88d4849b4f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -42,7 +42,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"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)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"ErrorBoundaries","severity":"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)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.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/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..9107c896cc 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":{"category":"EffectSetState","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\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":"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..cb5203c881 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":{"category":"EffectSetState","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\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":"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/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md index 5021e7978d..6a9b4d98e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md @@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":346},"end":{"line":9,"column":49,"index":393},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":368},"end":{"line":9,"column":27,"index":371},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md index 7bcc917447..00473c8eec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"severity":"InvalidReact","category":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","severity":"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)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md index dd275b857f..825bc4a1df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"severity":"InvalidReact","category":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} +{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect().","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}} {"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":191},"end":{"line":8,"column":14,"index":242},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":222},"end":{"line":7,"column":16,"index":225},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` 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..7d490de8c6 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,7 +54,7 @@ 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","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"detail":{"options":{"category":"Immutability","severity":"InvalidReact","reason":"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":"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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 1224a5b9cf..c0cac509c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -50,7 +50,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index 7bec7f73b5..337997caa5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index e7f9ad7f30..019beccdc8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -37,7 +37,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index f6220645ef..f673b27779 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -41,7 +41,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index 4a72cdd855..a44cf6a9f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -32,7 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"category":"StaticComponents","severity":"InvalidReact","reason":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 3424489275..2830d70d95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -12,9 +12,11 @@ export { CompilerDiagnostic, CompilerSuggestionOperation, ErrorSeverity, + LintRules, type CompilerErrorDetailOptions, type CompilerDiagnosticOptions, type CompilerDiagnosticDetail, + type LintRule, } from './CompilerError'; export { compileFn as compile, 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..a745397078 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ImpureFunctionCallsRule-test.ts @@ -0,0 +1,39 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no impure function calls rule', + allRules[getRuleForCategory(ErrorCategory.Purity).name], + { + 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('Cannot call impure function during render'), + makeTestCaseError('Cannot call impure function during render'), + makeTestCaseError('Cannot call impure function during render'), + ], + }, + ], + }, +); 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..0ba165011c --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/InvalidHooksRule-test.ts @@ -0,0 +1,100 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, makeTestCaseError, testRule} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'rules-of-hooks', + allRules[getRuleForCategory(ErrorCategory.Hooks).name], + { + 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: [ + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + ], + }, + { + name: 'Multiple diagnostics within the same function are surfaced', + code: normalizeIndent` + function useConditional() { + cond ?? useConditionalHook(); + props.cond && useConditionalHook(); + return
Hello world
; + }`, + errors: [ + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + makeTestCaseError( + 'Hooks must always be called in a consistent order', + ), + ], + }, + ], + }, +); 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..9a09f4c4e0 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoAmbiguousJsxRule-test.ts @@ -0,0 +1,38 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no ambiguous JSX rule', + allRules[getRuleForCategory(ErrorCategory.ErrorBoundaries).name], + { + 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('Avoid constructing JSX within try/catch')], + }, + ], + }, +); 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..c361670e97 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoCapitalizedCallsRule-test.ts @@ -0,0 +1,71 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, makeTestCaseError, testRule} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no-capitalized-calls', + allRules[getRuleForCategory(ErrorCategory.CapitalizedCalls).name], + { + valid: [], + invalid: [ + { + name: 'Simple violation', + code: normalizeIndent` + import Child from './Child'; + function Component() { + return <> + {Child()} + ; + } + `, + errors: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + { + name: 'Method call violation', + code: normalizeIndent` + import myModule from './MyModule'; + function Component() { + return <> + {myModule.Child()} + ; + } + `, + errors: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + { + 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: [ + makeTestCaseError( + 'Capitalized functions are reserved for components', + ), + ], + }, + ], + }, +); 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..9042980a80 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/NoRefAccessInRender-tests.ts @@ -0,0 +1,34 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import {normalizeIndent, testRule, makeTestCaseError} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +testRule( + 'no ref access in render rule', + allRules[getRuleForCategory(ErrorCategory.Refs).name], + { + 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('Cannot access refs during 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..6efd069aaf --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -0,0 +1,158 @@ +/** + * 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 { + ErrorCategory, + getRuleForCategory, +} from 'babel-plugin-react-compiler/src/CompilerError'; +import { + normalizeIndent, + testRule, + makeTestCaseError, + TestRecommendedRules, +} from './shared-utils'; +import {allRules} from '../src/rules/ReactCompilerRule'; + +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: [ + { + // TODO: actually return multiple diagnostics in this case + 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: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + 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: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + name: "'use no forget' does not disable eslint rule", + code: normalizeIndent` + let count = 0; + function Component() { + 'use no forget'; + return cond ?? useConditionalHook(); + + } + `, + errors: [ + makeTestCaseError('Hooks must always be called in a consistent order'), + ], + }, + { + 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('useMemo() callbacks must return a value')], + }, + { + 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/, + }, + ], + }, + ], +}); 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__/shared-utils.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts new file mode 100644 index 0000000000..2ab24581d8 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/shared-utils.ts @@ -0,0 +1,76 @@ +import {RuleTester as ESLintTester, Rule} from 'eslint'; +import {type ErrorCategory} from 'babel-plugin-react-compiler/src/CompilerError'; +import escape from 'regexp.escape'; +import {configs} from '../src/index'; +import {allRules} 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) + */ +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 function makeTestCaseError(reason: string): ESLintTester.TestCaseError { + return { + message: new RegExp(escape(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..1a339e8331 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/index.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/index.ts @@ -5,29 +5,26 @@ * LICENSE file in the root directory of this source tree. */ -import ReactCompilerRule from './rules/ReactCompilerRule'; +import {allRules, recommendedRules} from './rules/ReactCompilerRule'; const meta = { name: 'eslint-plugin-react-compiler', }; -const rules = { - 'react-compiler': ReactCompilerRule, -}; - const configs = { recommended: { plugins: { 'react-compiler': { - rules: { - 'react-compiler': ReactCompilerRule, - }, + rules: allRules, }, }, - 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..d2e8fdd42e 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,23 @@ * 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 { + LintRules, + type LintRule, +} 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 +75,95 @@ 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(rule: LintRule): Rule.RuleModule { + const create = (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.category === rule.category) { + 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 {}; + }; + + return { + meta: { + type: 'problem', + docs: { + description: rule.description, + recommended: rule.recommended, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create, + }; +} + +export const NoUnusedDirectivesRule: Rule.RuleModule = { meta: { - type: 'problem', + type: 'suggestion', docs: { - description: 'Surfaces diagnostics from React Forget', recommended: true, }, fixable: 'code', @@ -124,234 +171,47 @@ 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(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); - 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, - }); - } - - const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; - } - 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; - } - } - 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); - }, + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer): Rule.Fix { + return fixer.removeRange(directive.range); }, - ], - }); - } - } - 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; +type RulesObject = {[name: string]: Rule.RuleModule}; + +export const allRules: RulesObject = LintRules.reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); + +export const recommendedRules: RulesObject = LintRules.filter( + rule => rule.recommended, +).reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); 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..e8661d7f96 --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -0,0 +1,287 @@ +/** + * 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: Array; +}; + +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 + 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(); + +// 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; + + constructor(size: number) { + this.#values = new Array(size).fill(SENTINEL); + } + + // 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 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" diff --git a/fixtures/eslint-v9/eslint.config.ts b/fixtures/eslint-v9/eslint.config.ts index 62ef686716..66d2c08746 100644 --- a/fixtures/eslint-v9/eslint.config.ts +++ b/fixtures/eslint-v9/eslint.config.ts @@ -1,7 +1,9 @@ -import type {Linter} from 'eslint'; -import * as reactHooks from 'eslint-plugin-react-hooks'; +import {defineConfig} from 'eslint/config'; +import reactHooks from 'eslint-plugin-react-hooks'; -export default [ +console.log(reactHooks.configs['recommended-latest']); + +export default defineConfig([ { languageOptions: { ecmaVersion: 'latest', @@ -12,11 +14,12 @@ export default [ }, }, }, - }, - reactHooks.configs['recommended'], - { + plugins: { + 'react-hooks': reactHooks, + }, + extends: ['react-hooks/recommended-latest'], rules: { 'react-hooks/exhaustive-deps': 'error', }, }, -] satisfies Linter.Config[]; +]); diff --git a/fixtures/eslint-v9/package.json b/fixtures/eslint-v9/package.json index 80827a0d17..c09d7ec99b 100644 --- a/fixtures/eslint-v9/package.json +++ b/fixtures/eslint-v9/package.json @@ -2,7 +2,7 @@ "private": true, "name": "eslint-v9", "dependencies": { - "eslint": "^9.18.0", + "eslint": "^9.33.0", "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks", "jiti": "^2.4.2" }, diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock index 630bf074a3..a471aadd96 100644 --- a/fixtures/eslint-v9/yarn.lock +++ b/fixtures/eslint-v9/yarn.lock @@ -221,26 +221,31 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== +"@eslint/config-array@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== dependencies: "@eslint/object-schema" "^2.1.6" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e" - integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg== +"@eslint/config-helpers@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" + integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== + +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/eslintrc@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846" - integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ== +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -252,22 +257,22 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.21.0": - version "9.21.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.21.0.tgz#4303ef4e07226d87c395b8fad5278763e9c15c08" - integrity sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw== +"@eslint/js@9.33.0": + version "9.33.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368" + integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A== "@eslint/object-schema@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== -"@eslint/plugin-kit@^0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27" - integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g== +"@eslint/plugin-kit@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: - "@eslint/core" "^0.12.0" + "@eslint/core" "^0.15.2" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -350,6 +355,11 @@ acorn@^8.14.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -475,10 +485,10 @@ escape-string-regexp@^4.0.0: version "0.0.0" uid "" -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -493,18 +503,24 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.18.0: - version "9.21.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.21.0.tgz#b1c9c16f5153ff219791f627b94ab8f11f811591" - integrity sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.33.0: + version "9.33.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.33.0.tgz#cc186b3d9eb0e914539953d6a178a5b413997b73" + integrity sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.2" - "@eslint/core" "^0.12.0" - "@eslint/eslintrc" "^3.3.0" - "@eslint/js" "9.21.0" - "@eslint/plugin-kit" "^0.2.7" + "@eslint/config-array" "^0.21.0" + "@eslint/config-helpers" "^0.3.1" + "@eslint/core" "^0.15.2" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.33.0" + "@eslint/plugin-kit" "^0.3.5" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/retry" "^0.4.2" @@ -515,9 +531,9 @@ eslint@^9.18.0: cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -533,7 +549,7 @@ eslint@^9.18.0: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: +espree@^10.0.1: version "10.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== @@ -542,6 +558,15 @@ espree@^10.0.1, espree@^10.3.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.0" +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + esquery@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" diff --git a/package.json b/package.json index 7d191ee4e2..b73af395ba 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.10.4", "@babel/plugin-transform-object-super": "^7.10.4", "@babel/plugin-transform-parameters": "^7.10.5", + "@babel/plugin-transform-private-methods": "^7.10.4", "@babel/plugin-transform-react-jsx": "^7.23.4", "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.10.5", diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 7a6b09c544..812c2010a0 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -12,7 +12,8 @@ const ESLintTesterV7 = require('eslint-v7').RuleTester; const ESLintTesterV9 = require('eslint-v9').RuleTester; const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks'); -const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps']; +const ReactHooksESLintRule = + ReactHooksESLintPlugin.default.rules['exhaustive-deps']; /** * A string template tag that removes padding from the left side of multi-line strings diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index ac8886c776..8d8040bb43 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -12,7 +12,8 @@ const ESLintTesterV7 = require('eslint-v7').RuleTester; const ESLintTesterV9 = require('eslint-v9').RuleTester; const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks'); -const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['rules-of-hooks']; +const ReactHooksESLintRule = + ReactHooksESLintPlugin.default.rules['rules-of-hooks']; /** * A string template tag that removes padding from the left side of multi-line strings diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts deleted file mode 100644 index a687d49cc6..0000000000 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts +++ /dev/null @@ -1,289 +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'; -import {RuleTester as ESLintTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompiler'; - -const ESLintTesterV8 = require('eslint-v8').RuleTester; - -/** - * 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 ESLintTesterV8({ - parser: require.resolve('hermes-eslint'), - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }, -}); -eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts index 9d05cd1871..d9385bdba4 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts @@ -6,7 +6,7 @@ */ import {RuleTester} from 'eslint'; -import ReactCompilerRule from '../src/rules/ReactCompiler'; +import {allRules} from '../src/shared/ReactCompiler'; const ESLintTesterV8 = require('eslint-v8').RuleTester; @@ -74,4 +74,4 @@ const tests: CompilerTestCases = { const eslintTester = new ESLintTesterV8({ parser: require.resolve('@typescript-eslint/parser-v5'), }); -eslintTester.run('react-compiler', ReactCompilerRule, tests); +eslintTester.run('react-compiler', allRules['immutability'], tests); diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index 26a2e2b2c4..462c3104a7 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -4,63 +4,64 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import type {ESLint, Linter, Rule} from 'eslint'; +import type {Linter, Rule} from 'eslint'; import ExhaustiveDeps from './rules/ExhaustiveDeps'; -import ReactCompiler from './rules/ReactCompiler'; +import {allRules, recommendedRules} from './shared/ReactCompiler'; import RulesOfHooks from './rules/RulesOfHooks'; // All rules const rules = { 'exhaustive-deps': ExhaustiveDeps, - 'react-compiler': ReactCompiler, 'rules-of-hooks': RulesOfHooks, + ...allRules, } satisfies Record; // Config rules -const configRules = { +const ruleConfigs = { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', + ...Object.fromEntries( + Object.keys(recommendedRules).map(name => ['react-hooks/' + name, 'error']), + ), } satisfies Linter.RulesRecord; -// Flat config -const recommendedConfig = { - name: 'react-hooks/recommended', - plugins: { - get 'react-hooks'(): ESLint.Plugin { - return plugin; - }, +const plugin = { + meta: { + name: 'eslint-plugin-react-hooks', }, - rules: configRules, + configs: {}, + rules, }; -// Plugin object -const plugin = { - // TODO: Make this more dynamic to populate version from package.json. - // This can be done by injecting at build time, since importing the package.json isn't an option in Meta - meta: {name: 'eslint-plugin-react-hooks'}, - rules, - configs: { - /** Legacy recommended config, to be used with rc-based configurations */ - 'recommended-legacy': { - plugins: ['react-hooks'], - rules: configRules, - }, - - /** - * Recommended config, to be used with flat configs. - */ - recommended: recommendedConfig, - - /** @deprecated please use `recommended`; will be removed in v7 */ - 'recommended-latest': recommendedConfig, +Object.assign(plugin.configs, { + 'recommended-legacy': { + plugins: ['react-hooks'], + rules: ruleConfigs, }, -} satisfies ESLint.Plugin; -const configs = plugin.configs; -const meta = plugin.meta; -export {configs, meta, rules}; + 'flat/recommended': [ + { + plugins: { + 'react-hooks': plugin, + }, + rules: ruleConfigs, + }, + ], -// TODO: If the plugin is ever updated to be pure ESM and drops support for rc-based configs, then it should be exporting the plugin as default -// instead of individual named exports. -// export default plugin; + 'recommended-latest': [ + { + plugins: { + 'react-hooks': plugin, + }, + rules: ruleConfigs, + }, + ], + + recommended: { + plugins: ['react-hooks'], + rules: ruleConfigs, + }, +}); + +export default plugin; diff --git a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts deleted file mode 100644 index 795a117981..0000000000 --- a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts +++ /dev/null @@ -1,359 +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. - */ -/* eslint-disable no-for-of-loops/no-for-of-loops */ - -import {transformFromAstSync} from '@babel/core'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-transform-private-methods'; -import type {SourceLocation as BabelSourceLocation} from '@babel/types'; -import BabelPluginReactCompiler, { - type CompilerErrorDetail, - type CompilerErrorDetailOptions, - type CompilerDiagnostic, - type CompilerDiagnosticOptions, - CompilerSuggestionOperation, - ErrorSeverity, - parsePluginOptions, - validateEnvironmentConfig, - OPT_OUT_DIRECTIVES, - type Logger, - type LoggerEvent, - type PluginOptions, -} from 'babel-plugin-react-compiler'; -import type {Rule} from 'eslint'; -import {Statement} from 'estree'; -import * as HermesParser from 'hermes-parser'; - -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 { - const suggest: Array = []; - if (Array.isArray(detail.suggestions)) { - for (const suggestion of detail.suggestions) { - switch (suggestion.op) { - case CompilerSuggestionOperation.InsertBefore: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.insertTextBeforeRange( - suggestion.range, - suggestion.text, - ); - }, - }); - break; - case CompilerSuggestionOperation.InsertAfter: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.insertTextAfterRange( - suggestion.range, - suggestion.text, - ); - }, - }); - break; - case CompilerSuggestionOperation.Replace: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.replaceTextRange(suggestion.range, suggestion.text); - }, - }); - break; - case CompilerSuggestionOperation.Remove: - suggest.push({ - desc: suggestion.description, - fix(fixer) { - return fixer.removeRange(suggestion.range); - }, - }); - break; - default: - assertExhaustive(suggestion, 'Unhandled suggestion operation'); - } - } - } - 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, - }), -}; - -const rule: Rule.RuleModule = { - meta: { - type: 'problem', - docs: { - description: 'Surfaces diagnostics from React Forget', - recommended: true, - }, - fixable: 'code', - hasSuggestions: true, - // 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; - } - - 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, - }); - } - - const loc = detail.primaryLocation(); - if ( - !isReportableDiagnostic(detail) || - loc == null || - typeof loc === 'symbol' - ) { - return; - } - 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; - } - } - 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 {}; - } - }, -}; - -export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts new file mode 100644 index 0000000000..f006b79781 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts @@ -0,0 +1,216 @@ +/** + * 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. + */ +/* eslint-disable no-for-of-loops/no-for-of-loops */ + +import type {SourceLocation as BabelSourceLocation} from '@babel/types'; +import { + type CompilerDiagnosticOptions, + type CompilerErrorDetailOptions, + CompilerSuggestionOperation, + LintRules, + type LintRule, +} from 'babel-plugin-react-compiler'; +import type {Rule} from 'eslint'; +import runReactCompiler, {RunCacheEntry} from './RunReactCompiler'; + +function assertExhaustive(_: never, errorMsg: string): never { + throw new Error(errorMsg); +} + +function makeSuggestions( + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, +): Array { + const suggest: Array = []; + if (Array.isArray(detail.suggestions)) { + for (const suggestion of detail.suggestions) { + switch (suggestion.op) { + case CompilerSuggestionOperation.InsertBefore: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.insertTextBeforeRange( + suggestion.range, + suggestion.text, + ); + }, + }); + break; + case CompilerSuggestionOperation.InsertAfter: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.insertTextAfterRange( + suggestion.range, + suggestion.text, + ); + }, + }); + break; + case CompilerSuggestionOperation.Replace: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.replaceTextRange(suggestion.range, suggestion.text); + }, + }); + break; + case CompilerSuggestionOperation.Remove: + suggest.push({ + desc: suggestion.description, + fix(fixer) { + return fixer.removeRange(suggestion.range); + }, + }); + break; + default: + assertExhaustive(suggestion, 'Unhandled suggestion operation'); + } + } + } + return suggest; +} + +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 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(rule: LintRule): Rule.RuleModule { + const create = (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.category === rule.category) { + 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 {}; + }; + + return { + meta: { + type: 'problem', + docs: { + description: rule.description, + recommended: rule.recommended, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create, + }; +} + +export const NoUnusedDirectivesRule: Rule.RuleModule = { + meta: { + type: 'suggestion', + docs: { + recommended: true, + }, + fixable: 'code', + hasSuggestions: true, + // validation is done at runtime with zod + schema: [{type: 'object', additionalProperties: true}], + }, + create(context: Rule.RuleContext): Rule.RuleListener { + const results = getReactCompilerResult(context); + + for (const directive of results.unusedOptOutDirectives) { + context.report({ + message: `Unused '${directive.directive}' directive`, + loc: directive.loc, + suggest: [ + { + desc: 'Remove the directive', + fix(fixer): Rule.Fix { + return fixer.removeRange(directive.range); + }, + }, + ], + }); + } + return {}; + }, +}; + +type RulesObject = {[name: string]: Rule.RuleModule}; + +export const allRules: RulesObject = LintRules.reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); + +export const recommendedRules: RulesObject = LintRules.filter( + rule => rule.recommended, +).reduce( + (acc, rule) => { + acc[rule.name] = makeRule(rule); + return acc; + }, + { + 'no-unused-directives': NoUnusedDirectivesRule, + } as RulesObject, +); diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts new file mode 100644 index 0000000000..72e28b1f48 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -0,0 +1,281 @@ +/** + * 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. + */ +/* eslint-disable no-for-of-loops/no-for-of-loops */ + +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, + Logger, + LoggerEvent, +} from 'babel-plugin-react-compiler'; +import type {SourceCode} from 'eslint'; +import {SourceLocation} from 'estree'; +import * as HermesParser from 'hermes-parser'; +import {isDeepStrictEqual} from 'util'; +import type {ParseResult} from '@babel/parser'; + +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: Array; +}; + +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 + 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) { + 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(); + +// 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; + + constructor(size: number) { + this.#values = new Array(size).fill(SENTINEL); + } + + // gets a value and sets it as "recently used" + get(key: K): T | null { + const 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 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; + } + + 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/scripts/rollup/build.js b/scripts/rollup/build.js index ac9ec4fa77..93c02ce844 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -403,6 +403,7 @@ function getPlugins( // Use Node resolution mechanism. resolve({ // skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0 + preferBuiltins: bundle.preferBuiltins, }), // Remove license headers from individual modules stripBanner({ diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 63e702f77f..749548f8ec 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1208,7 +1208,14 @@ const bundles = [ global: 'ESLintPluginReactHooks', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false, - externals: [], + preferBuiltins: true, + externals: [ + '@babel/core', + '@babel/plugin-proposal-private-methods', + 'hermes-parser', + 'zod', + 'zod-validation-error', + ], tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json', prebuild: `mkdir -p ./compiler/packages/babel-plugin-react-compiler/dist && echo "module.exports = require('../src/index.ts');" > ./compiler/packages/babel-plugin-react-compiler/dist/index.js`, }, @@ -1296,9 +1303,21 @@ function getFilename(bundle, bundleType) { } } +let activeBundles = bundles; +if (process.env.BUNDLES_FILTER != null) { + activeBundles = activeBundles.filter( + bundle => bundle.name === process.env.BUNDLES_FILTER + ); + if (activeBundles.length === 0) { + throw new Error( + `No bundles matched for BUNDLES_FILTER=${process.env.BUNDLES_FILTER}` + ); + } +} + module.exports = { bundleTypes, moduleTypes, - bundles, + bundles: activeBundles, getFilename, }; diff --git a/yarn.lock b/yarn.lock index f79e4479cb..a1ab0f5cc4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,6 +70,15 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -197,6 +206,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + "@babel/generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" @@ -235,6 +255,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -343,6 +370,19 @@ "@babel/traverse" "^7.26.9" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.27.1": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46" + integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.3" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -449,6 +489,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + "@babel/helper-hoist-variables@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" @@ -478,6 +523,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-member-expression-to-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" + integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-module-imports@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -561,6 +614,13 @@ dependencies: "@babel/types" "^7.25.9" +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" @@ -581,6 +641,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + "@babel/helper-plugin-utils@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" @@ -644,6 +709,15 @@ "@babel/helper-optimise-call-expression" "^7.25.9" "@babel/traverse" "^7.26.5" +"@babel/helper-replace-supers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" + integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/helper-simple-access@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" @@ -681,6 +755,14 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" @@ -707,6 +789,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + "@babel/helper-validator-identifier@^7.14.0", "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" @@ -722,6 +809,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" @@ -849,6 +941,13 @@ dependencies: "@babel/types" "^7.26.10" +"@babel/parser@^7.27.2", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" @@ -1765,6 +1864,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" +"@babel/plugin-transform-private-methods@^7.10.4": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" + integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/plugin-transform-private-methods@^7.24.4", "@babel/plugin-transform-private-methods@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" @@ -2366,6 +2473,15 @@ "@babel/parser" "^7.26.9" "@babel/types" "^7.26.9" +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -2446,6 +2562,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.13", "@babel/types@^7.12.5", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" @@ -2462,6 +2591,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + 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.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3047,6 +3184,14 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.12": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.8" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" @@ -3119,6 +3264,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -8100,7 +8253,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" -"eslint-v7@npm:eslint@^7.7.0": +"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -8299,52 +8452,6 @@ eslint@8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" -eslint@^7.7.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - espree@10.0.1, espree@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" @@ -15960,7 +16067,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15995,15 +16102,6 @@ string-width@^4.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16064,7 +16162,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16092,13 +16190,6 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -17681,7 +17772,7 @@ workerize-loader@^2.0.2: dependencies: loader-utils "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17699,15 +17790,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"