mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[compiler] Aggregate error reporting, separate eslint rules (#34176)
NOTE: this is a merged version of @mofeiZ's original PR along with my edits per offline discussion. The description is updated to reflect the latest approach. The key problem we're trying to solve with this PR is to allow developers more control over the compiler's various validations. The idea is to have a number of rules targeting a specific category of issues, such as enforcing immutability of props/state/etc or disallowing access to refs during render. We don't want to have to run the compiler again for every single rule, though, so @mofeiZ added an LRU cache that caches the full compilation output of N most recent files. The first rule to run on a given file will cause it to get cached, and then subsequent rules can pull from the cache, with each rule filtering down to its specific category of errors. For the categories, I went through and assigned a category roughly 1:1 to existing validations, and then used my judgement on some places that felt distinct enough to warrant a separate error. Every error in the compiler now has to supply both a severity (for legacy reasons) and a category (for ESLint). Each category corresponds 1:1 to a ESLint rule definition, so that the set of rules is automatically populated based on the defined categories. Categories include a flag for whether they should be in the recommended set or not. Note that as with the original version of this PR, only eslint-plugin-react-compiler is changed. We still have to update the main lint rule. ## Test Plan * Created a sample project using ESLint v9 and verified that the plugin can be configured correctly and detects errors * Edited `fixtures/eslint-v9` and introduced errors, verified that the w latest config changes in that fixture it correctly detects the errors * In the sample project, confirmed that the LRU caching is correctly caching compiler output, ie compiling files just once. Co-authored-by: Mofei Zhang <feifei0@meta.com>
This commit is contained in:
parent
253abc78a1
commit
7d29ecbeb2
|
|
@ -8,6 +8,7 @@ module.exports = {
|
||||||
'@babel/plugin-syntax-jsx',
|
'@babel/plugin-syntax-jsx',
|
||||||
'@babel/plugin-transform-flow-strip-types',
|
'@babel/plugin-transform-flow-strip-types',
|
||||||
['@babel/plugin-transform-class-properties', {loose: true}],
|
['@babel/plugin-transform-class-properties', {loose: true}],
|
||||||
|
['@babel/plugin-transform-private-methods', {loose: true}],
|
||||||
'@babel/plugin-transform-classes',
|
'@babel/plugin-transform-classes',
|
||||||
],
|
],
|
||||||
presets: [
|
presets: [
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ export enum ErrorSeverity {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompilerDiagnosticOptions = {
|
export type CompilerDiagnosticOptions = {
|
||||||
|
category: ErrorCategory;
|
||||||
severity: ErrorSeverity;
|
severity: ErrorSeverity;
|
||||||
category: string;
|
reason: string;
|
||||||
description: string;
|
description: string;
|
||||||
details: Array<CompilerDiagnosticDetail>;
|
details: Array<CompilerDiagnosticDetail>;
|
||||||
suggestions?: Array<CompilerSuggestion> | null | undefined;
|
suggestions?: Array<CompilerSuggestion> | null | undefined;
|
||||||
|
|
@ -91,9 +92,10 @@ export type CompilerSuggestion =
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CompilerErrorDetailOptions = {
|
export type CompilerErrorDetailOptions = {
|
||||||
|
category: ErrorCategory;
|
||||||
|
severity: ErrorSeverity;
|
||||||
reason: string;
|
reason: string;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
severity: ErrorSeverity;
|
|
||||||
loc: SourceLocation | null;
|
loc: SourceLocation | null;
|
||||||
suggestions?: Array<CompilerSuggestion> | null | undefined;
|
suggestions?: Array<CompilerSuggestion> | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
@ -119,8 +121,8 @@ export class CompilerDiagnostic {
|
||||||
return new CompilerDiagnostic({...options, details: []});
|
return new CompilerDiagnostic({...options, details: []});
|
||||||
}
|
}
|
||||||
|
|
||||||
get category(): CompilerDiagnosticOptions['category'] {
|
get reason(): CompilerDiagnosticOptions['reason'] {
|
||||||
return this.options.category;
|
return this.options.reason;
|
||||||
}
|
}
|
||||||
get description(): CompilerDiagnosticOptions['description'] {
|
get description(): CompilerDiagnosticOptions['description'] {
|
||||||
return this.options.description;
|
return this.options.description;
|
||||||
|
|
@ -131,6 +133,9 @@ export class CompilerDiagnostic {
|
||||||
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
|
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
|
||||||
return this.options.suggestions;
|
return this.options.suggestions;
|
||||||
}
|
}
|
||||||
|
get category(): ErrorCategory {
|
||||||
|
return this.options.category;
|
||||||
|
}
|
||||||
|
|
||||||
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
|
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
|
||||||
this.options.details.push(detail);
|
this.options.details.push(detail);
|
||||||
|
|
@ -148,7 +153,7 @@ export class CompilerDiagnostic {
|
||||||
|
|
||||||
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
|
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
|
||||||
const buffer = [
|
const buffer = [
|
||||||
printErrorSummary(this.severity, this.category),
|
printErrorSummary(this.severity, this.reason),
|
||||||
'\n\n',
|
'\n\n',
|
||||||
this.description,
|
this.description,
|
||||||
];
|
];
|
||||||
|
|
@ -193,7 +198,7 @@ export class CompilerDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
const buffer = [printErrorSummary(this.severity, this.category)];
|
const buffer = [printErrorSummary(this.severity, this.reason)];
|
||||||
if (this.description != null) {
|
if (this.description != null) {
|
||||||
buffer.push(`. ${this.description}.`);
|
buffer.push(`. ${this.description}.`);
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +236,9 @@ export class CompilerErrorDetail {
|
||||||
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
|
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
|
||||||
return this.options.suggestions;
|
return this.options.suggestions;
|
||||||
}
|
}
|
||||||
|
get category(): ErrorCategory {
|
||||||
|
return this.options.category;
|
||||||
|
}
|
||||||
|
|
||||||
primaryLocation(): SourceLocation | null {
|
primaryLocation(): SourceLocation | null {
|
||||||
return this.loc;
|
return this.loc;
|
||||||
|
|
@ -280,13 +288,14 @@ export class CompilerError extends Error {
|
||||||
|
|
||||||
static invariant(
|
static invariant(
|
||||||
condition: unknown,
|
condition: unknown,
|
||||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||||
): asserts condition {
|
): asserts condition {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
const errors = new CompilerError();
|
const errors = new CompilerError();
|
||||||
errors.pushErrorDetail(
|
errors.pushErrorDetail(
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
...options,
|
...options,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -301,23 +310,28 @@ export class CompilerError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
static throwTodo(
|
static throwTodo(
|
||||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||||
): never {
|
): never {
|
||||||
const errors = new CompilerError();
|
const errors = new CompilerError();
|
||||||
errors.pushErrorDetail(
|
errors.pushErrorDetail(
|
||||||
new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}),
|
new CompilerErrorDetail({
|
||||||
|
...options,
|
||||||
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
throw errors;
|
throw errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
static throwInvalidJS(
|
static throwInvalidJS(
|
||||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||||
): never {
|
): never {
|
||||||
const errors = new CompilerError();
|
const errors = new CompilerError();
|
||||||
errors.pushErrorDetail(
|
errors.pushErrorDetail(
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
...options,
|
...options,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
throw errors;
|
throw errors;
|
||||||
|
|
@ -337,13 +351,14 @@ export class CompilerError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
static throwInvalidConfig(
|
static throwInvalidConfig(
|
||||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||||
): never {
|
): never {
|
||||||
const errors = new CompilerError();
|
const errors = new CompilerError();
|
||||||
errors.pushErrorDetail(
|
errors.pushErrorDetail(
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
...options,
|
...options,
|
||||||
severity: ErrorSeverity.InvalidConfig,
|
severity: ErrorSeverity.InvalidConfig,
|
||||||
|
category: ErrorCategory.Config,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
throw errors;
|
throw errors;
|
||||||
|
|
@ -407,6 +422,7 @@ export class CompilerError extends Error {
|
||||||
|
|
||||||
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
|
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
|
||||||
const detail = new CompilerErrorDetail({
|
const detail = new CompilerErrorDetail({
|
||||||
|
category: options.category,
|
||||||
reason: options.reason,
|
reason: options.reason,
|
||||||
description: options.description ?? null,
|
description: options.description ?? null,
|
||||||
severity: options.severity,
|
severity: options.severity,
|
||||||
|
|
@ -507,3 +523,327 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string {
|
||||||
}
|
}
|
||||||
return `${severityCategory}: ${message}`;
|
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>"
|
||||||
|
*/
|
||||||
|
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<LintRule> = Object.keys(ErrorCategory).map(
|
||||||
|
category => getRuleForCategory(category as any),
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {NodePath} from '@babel/core';
|
||||||
import * as t from '@babel/types';
|
import * as t from '@babel/types';
|
||||||
import {Scope as BabelScope} from '@babel/traverse';
|
import {Scope as BabelScope} from '@babel/traverse';
|
||||||
|
|
||||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
EnvironmentConfig,
|
EnvironmentConfig,
|
||||||
GeneratedSource,
|
GeneratedSource,
|
||||||
|
|
@ -38,6 +38,7 @@ export function validateRestrictedImports(
|
||||||
ImportDeclaration(importDeclPath) {
|
ImportDeclaration(importDeclPath) {
|
||||||
if (restrictedImports.has(importDeclPath.node.source.value)) {
|
if (restrictedImports.has(importDeclPath.node.source.value)) {
|
||||||
error.push({
|
error.push({
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
reason: 'Bailing out due to blocklisted import',
|
reason: 'Bailing out due to blocklisted import',
|
||||||
description: `Import from module ${importDeclPath.node.source.value}`,
|
description: `Import from module ${importDeclPath.node.source.value}`,
|
||||||
|
|
@ -205,6 +206,7 @@ export class ProgramContext {
|
||||||
}
|
}
|
||||||
const error = new CompilerError();
|
const error = new CompilerError();
|
||||||
error.push({
|
error.push({
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
reason: 'Encountered conflicting global in generated program',
|
reason: 'Encountered conflicting global in generated program',
|
||||||
description: `Conflict from local binding ${name}`,
|
description: `Conflict from local binding ${name}`,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import * as t from '@babel/types';
|
||||||
import {
|
import {
|
||||||
CompilerError,
|
CompilerError,
|
||||||
CompilerErrorDetail,
|
CompilerErrorDetail,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {ExternalFunction, ReactFunctionType} from '../HIR/Environment';
|
import {ExternalFunction, ReactFunctionType} from '../HIR/Environment';
|
||||||
|
|
@ -105,6 +106,7 @@ function findDirectivesDynamicGating(
|
||||||
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
|
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
|
||||||
description: `Found '${directive.value.value}'`,
|
description: `Found '${directive.value.value}'`,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Gating,
|
||||||
loc: directive.loc ?? null,
|
loc: directive.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -121,6 +123,7 @@ function findDirectivesDynamicGating(
|
||||||
.map(r => r.directive.value.value)
|
.map(r => r.directive.value.value)
|
||||||
.join(', ')}]`,
|
.join(', ')}]`,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Gating,
|
||||||
loc: result[0].directive.loc ?? null,
|
loc: result[0].directive.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -456,6 +459,7 @@ export function compileProgram(
|
||||||
reason:
|
reason:
|
||||||
'Unexpected compiled functions when module scope opt-out is present',
|
'Unexpected compiled functions when module scope opt-out is present',
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
loc: null,
|
loc: null,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -811,6 +815,7 @@ function shouldSkipCompilation(
|
||||||
description:
|
description:
|
||||||
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
|
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
|
||||||
severity: ErrorSeverity.InvalidConfig,
|
severity: ErrorSeverity.InvalidConfig,
|
||||||
|
category: ErrorCategory.Config,
|
||||||
loc: null,
|
loc: null,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
CompilerSuggestionOperation,
|
CompilerSuggestionOperation,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {assertExhaustive} from '../Utils/utils';
|
import {assertExhaustive} from '../Utils/utils';
|
||||||
|
|
@ -183,9 +184,10 @@ export function suppressionsToCompilerError(
|
||||||
}
|
}
|
||||||
error.pushDiagnostic(
|
error.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
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()}\``,
|
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,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Suppression,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
description: suggestion,
|
description: suggestion,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ import {getOrInsertWith} from '../Utils/utils';
|
||||||
import {Environment, GeneratedSource} from '../HIR';
|
import {Environment, GeneratedSource} from '../HIR';
|
||||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||||
import {CompileProgramMetadata} from './Program';
|
import {CompileProgramMetadata} from './Program';
|
||||||
import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError';
|
import {
|
||||||
|
CompilerDiagnostic,
|
||||||
|
CompilerDiagnosticOptions,
|
||||||
|
ErrorCategory,
|
||||||
|
} from '../CompilerError';
|
||||||
|
|
||||||
function throwInvalidReact(
|
function throwInvalidReact(
|
||||||
options: Omit<CompilerDiagnosticOptions, 'severity'>,
|
options: Omit<CompilerDiagnosticOptions, 'severity'>,
|
||||||
|
|
@ -92,7 +96,8 @@ function assertValidEffectImportReference(
|
||||||
*/
|
*/
|
||||||
throwInvalidReact(
|
throwInvalidReact(
|
||||||
{
|
{
|
||||||
category:
|
category: ErrorCategory.AutomaticEffectDependencies,
|
||||||
|
reason:
|
||||||
'Cannot infer dependencies of this effect. This will break your build!',
|
'Cannot infer dependencies of this effect. This will break your build!',
|
||||||
description:
|
description:
|
||||||
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
|
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
|
||||||
|
|
@ -123,8 +128,8 @@ function assertValidFireImportReference(
|
||||||
);
|
);
|
||||||
throwInvalidReact(
|
throwInvalidReact(
|
||||||
{
|
{
|
||||||
category:
|
category: ErrorCategory.Fire,
|
||||||
'[Fire] Untransformed reference to compiler-required feature.',
|
reason: '[Fire] Untransformed reference to compiler-required feature.',
|
||||||
description:
|
description:
|
||||||
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
|
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
|
||||||
maybeErrorDiagnostic
|
maybeErrorDiagnostic
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
CompilerSuggestionOperation,
|
CompilerSuggestionOperation,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {Err, Ok, Result} from '../Utils/Result';
|
import {Err, Ok, Result} from '../Utils/Result';
|
||||||
|
|
@ -108,7 +109,8 @@ export function lower(
|
||||||
builder.errors.pushDiagnostic(
|
builder.errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
severity: ErrorSeverity.Invariant,
|
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}\`.`,
|
description: `[BuildHIR] Could not find binding for param \`${param.node.name}\`.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -172,7 +174,8 @@ export function lower(
|
||||||
builder.errors.pushDiagnostic(
|
builder.errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
severity: ErrorSeverity.Todo,
|
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.`,
|
description: `[BuildHIR] Add support for ${param.node.type} parameters.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -203,7 +206,8 @@ export function lower(
|
||||||
builder.errors.pushDiagnostic(
|
builder.errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
severity: ErrorSeverity.InvalidJS,
|
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}\`.`,
|
description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -273,6 +277,7 @@ function lowerStatement(
|
||||||
reason:
|
reason:
|
||||||
'(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch',
|
'(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch',
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -460,6 +465,7 @@ function lowerStatement(
|
||||||
} else if (!binding.path.isVariableDeclarator()) {
|
} else if (!binding.path.isVariableDeclarator()) {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason: 'Unsupported declaration type for hoisting',
|
reason: 'Unsupported declaration type for hoisting',
|
||||||
description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`,
|
description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
|
|
@ -469,6 +475,7 @@ function lowerStatement(
|
||||||
} else {
|
} else {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason: 'Handle non-const declarations for hoisting',
|
reason: 'Handle non-const declarations for hoisting',
|
||||||
description: `variable "${binding.identifier.name}" declared with ${binding.kind}`,
|
description: `variable "${binding.identifier.name}" declared with ${binding.kind}`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
|
|
@ -549,6 +556,7 @@ function lowerStatement(
|
||||||
reason:
|
reason:
|
||||||
'(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement',
|
'(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement',
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -621,6 +629,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
|
reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -772,6 +781,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,
|
reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: case_.node.loc ?? null,
|
loc: case_.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -844,6 +854,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
|
reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -872,6 +883,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
loc: id.node.loc ?? null,
|
loc: id.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -889,6 +901,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Expect \`const\` declaration not to be reassigned`,
|
reason: `Expect \`const\` declaration not to be reassigned`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: id.node.loc ?? null,
|
loc: id.node.loc ?? null,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
|
|
@ -936,6 +949,7 @@ function lowerStatement(
|
||||||
reason: `Expected variable declaration to be an identifier if no initializer was provided`,
|
reason: `Expected variable declaration to be an identifier if no initializer was provided`,
|
||||||
description: `Got a \`${id.type}\``,
|
description: `Got a \`${id.type}\``,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1044,6 +1058,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerStatement) Handle for-await loops`,
|
reason: `(BuildHIR::lowerStatement) Handle for-await loops`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1276,6 +1291,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
|
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1285,6 +1301,7 @@ function lowerStatement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
|
reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.node.loc ?? null,
|
loc: stmt.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1378,6 +1395,7 @@ function lowerStatement(
|
||||||
reason: `JavaScript 'with' syntax is not supported`,
|
reason: `JavaScript 'with' syntax is not supported`,
|
||||||
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
|
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
|
||||||
severity: ErrorSeverity.UnsupportedJS,
|
severity: ErrorSeverity.UnsupportedJS,
|
||||||
|
category: ErrorCategory.UnsupportedSyntax,
|
||||||
loc: stmtPath.node.loc ?? null,
|
loc: stmtPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1398,6 +1416,7 @@ function lowerStatement(
|
||||||
reason: 'Inline `class` declarations are not supported',
|
reason: 'Inline `class` declarations are not supported',
|
||||||
description: `Move class declarations outside of components/hooks`,
|
description: `Move class declarations outside of components/hooks`,
|
||||||
severity: ErrorSeverity.UnsupportedJS,
|
severity: ErrorSeverity.UnsupportedJS,
|
||||||
|
category: ErrorCategory.UnsupportedSyntax,
|
||||||
loc: stmtPath.node.loc ?? null,
|
loc: stmtPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1427,6 +1446,7 @@ function lowerStatement(
|
||||||
reason:
|
reason:
|
||||||
'JavaScript `import` and `export` statements may only appear at the top level of a module',
|
'JavaScript `import` and `export` statements may only appear at the top level of a module',
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: stmtPath.node.loc ?? null,
|
loc: stmtPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1442,6 +1462,7 @@ function lowerStatement(
|
||||||
reason:
|
reason:
|
||||||
'TypeScript `namespace` statements may only appear at the top level of a module',
|
'TypeScript `namespace` statements may only appear at the top level of a module',
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: stmtPath.node.loc ?? null,
|
loc: stmtPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1520,6 +1541,7 @@ function lowerObjectPropertyKey(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: key.node.loc ?? null,
|
loc: key.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1545,6 +1567,7 @@ function lowerObjectPropertyKey(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: key.node.loc ?? null,
|
loc: key.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1602,6 +1625,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: valuePath.node.loc ?? null,
|
loc: valuePath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1628,6 +1652,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: propertyPath.node.loc ?? null,
|
loc: propertyPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1649,6 +1674,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: propertyPath.node.loc ?? null,
|
loc: propertyPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1682,6 +1708,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: element.node.loc ?? null,
|
loc: element.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1702,6 +1729,7 @@ function lowerExpression(
|
||||||
reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,
|
reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,
|
||||||
description: `Got a \`${calleePath.node.type}\``,
|
description: `Got a \`${calleePath.node.type}\``,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: calleePath.node.loc ?? null,
|
loc: calleePath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1728,6 +1756,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
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`,
|
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,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: calleePath.node.loc ?? null,
|
loc: calleePath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1762,6 +1791,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
|
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: leftPath.node.loc ?? null,
|
loc: leftPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1774,6 +1804,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Pipe operator not supported`,
|
reason: `(BuildHIR::lowerExpression) Pipe operator not supported`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: leftPath.node.loc ?? null,
|
loc: leftPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -1803,6 +1834,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Expected sequence expression to have at least one expression`,
|
reason: `Expected sequence expression to have at least one expression`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2015,6 +2047,7 @@ function lowerExpression(
|
||||||
reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`,
|
reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`,
|
||||||
description: `Expected an LVal, got: ${left.type}`,
|
description: `Expected an LVal, got: ${left.type}`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: left.node.loc ?? null,
|
loc: left.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2043,6 +2076,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2142,6 +2176,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
|
reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2181,6 +2216,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
|
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: attribute.node.loc ?? null,
|
loc: attribute.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2194,6 +2230,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``,
|
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: namePath.node.loc ?? null,
|
loc: namePath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2224,6 +2261,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
|
reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: valueExpr.node?.loc ?? null,
|
loc: valueExpr.node?.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2234,6 +2272,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
|
reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: valueExpr.node.loc ?? null,
|
loc: valueExpr.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2291,7 +2330,8 @@ function lowerExpression(
|
||||||
if (locations.length > 1) {
|
if (locations.length > 1) {
|
||||||
CompilerError.throwDiagnostic({
|
CompilerError.throwDiagnostic({
|
||||||
severity: ErrorSeverity.Todo,
|
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`,
|
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
|
||||||
details: locations.map(loc => {
|
details: locations.map(loc => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -2352,6 +2392,7 @@ function lowerExpression(
|
||||||
reason:
|
reason:
|
||||||
'(BuildHIR::lowerExpression) Handle tagged template with interpolations',
|
'(BuildHIR::lowerExpression) Handle tagged template with interpolations',
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2370,6 +2411,7 @@ function lowerExpression(
|
||||||
reason:
|
reason:
|
||||||
'(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value',
|
'(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value',
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2392,6 +2434,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Unexpected quasi and subexpression lengths in template literal`,
|
reason: `Unexpected quasi and subexpression lengths in template literal`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2402,6 +2445,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
|
reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2444,6 +2488,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Only object properties can be deleted`,
|
reason: `Only object properties can be deleted`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
|
|
@ -2459,6 +2504,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Throw expressions are not supported`,
|
reason: `Throw expressions are not supported`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
|
|
@ -2580,6 +2626,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
|
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2588,6 +2635,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`,
|
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2608,6 +2656,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`,
|
reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
loc: exprLoc,
|
loc: exprLoc,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2617,6 +2666,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global`,
|
reason: `(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprLoc,
|
loc: exprLoc,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2672,6 +2722,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`,
|
reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2681,6 +2732,7 @@ function lowerExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
|
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2978,6 +3030,7 @@ function lowerReorderableExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type \`${expr.type}\` cannot be safely reordered`,
|
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type \`${expr.type}\` cannot be safely reordered`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: expr.node.loc ?? null,
|
loc: expr.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3174,6 +3227,7 @@ function lowerArguments(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
|
reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: argPath.node.loc ?? null,
|
loc: argPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3209,6 +3263,7 @@ function lowerMemberExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
|
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: propertyNode.node.loc ?? null,
|
loc: propertyNode.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3230,6 +3285,7 @@ function lowerMemberExpression(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
|
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: propertyNode.node.loc ?? null,
|
loc: propertyNode.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3289,6 +3345,7 @@ function lowerJsxElementName(
|
||||||
reason: `Expected JSXNamespacedName to have no colons in the namespace or name`,
|
reason: `Expected JSXNamespacedName to have no colons in the namespace or name`,
|
||||||
description: `Got \`${namespace}\` : \`${name}\``,
|
description: `Got \`${namespace}\` : \`${name}\``,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3303,6 +3360,7 @@ function lowerJsxElementName(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
|
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3401,6 +3459,7 @@ function lowerJsxElement(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
|
reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3588,6 +3647,7 @@ function lowerIdentifier(
|
||||||
description:
|
description:
|
||||||
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
|
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
|
||||||
severity: ErrorSeverity.UnsupportedJS,
|
severity: ErrorSeverity.UnsupportedJS,
|
||||||
|
category: ErrorCategory.UnsupportedSyntax,
|
||||||
loc: exprPath.node.loc ?? null,
|
loc: exprPath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3644,6 +3704,7 @@ function lowerIdentifierForAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
loc: path.node.loc ?? null,
|
loc: path.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3656,6 +3717,7 @@ function lowerIdentifierForAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Cannot reassign a \`const\` variable`,
|
reason: `Cannot reassign a \`const\` variable`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: path.node.loc ?? null,
|
loc: path.node.loc ?? null,
|
||||||
description:
|
description:
|
||||||
binding.identifier.name != null
|
binding.identifier.name != null
|
||||||
|
|
@ -3713,6 +3775,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: lvalue.node.loc ?? null,
|
loc: lvalue.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3727,6 +3790,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `Unexpected context variable kind`,
|
reason: `Unexpected context variable kind`,
|
||||||
severity: ErrorSeverity.InvalidJS,
|
severity: ErrorSeverity.InvalidJS,
|
||||||
|
category: ErrorCategory.Syntax,
|
||||||
loc: lvalue.node.loc ?? null,
|
loc: lvalue.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3798,6 +3862,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
|
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: property.node.loc ?? null,
|
loc: property.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3810,6 +3875,7 @@ function lowerAssignment(
|
||||||
reason:
|
reason:
|
||||||
'(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property',
|
'(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property',
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: property.node.loc ?? null,
|
loc: property.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -3875,6 +3941,7 @@ function lowerAssignment(
|
||||||
} else if (identifier.kind === 'Global') {
|
} else if (identifier.kind === 'Global') {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason:
|
reason:
|
||||||
'Expected reassignment of globals to enable forceTemporaries',
|
'Expected reassignment of globals to enable forceTemporaries',
|
||||||
loc: element.node.loc ?? GeneratedSource,
|
loc: element.node.loc ?? GeneratedSource,
|
||||||
|
|
@ -3914,6 +3981,7 @@ function lowerAssignment(
|
||||||
} else if (identifier.kind === 'Global') {
|
} else if (identifier.kind === 'Global') {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason:
|
reason:
|
||||||
'Expected reassignment of globals to enable forceTemporaries',
|
'Expected reassignment of globals to enable forceTemporaries',
|
||||||
loc: element.node.loc ?? GeneratedSource,
|
loc: element.node.loc ?? GeneratedSource,
|
||||||
|
|
@ -3987,6 +4055,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ObjectPattern`,
|
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ObjectPattern`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: argument.node.loc ?? null,
|
loc: argument.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -4018,6 +4087,7 @@ function lowerAssignment(
|
||||||
} else if (identifier.kind === 'Global') {
|
} else if (identifier.kind === 'Global') {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason:
|
reason:
|
||||||
'Expected reassignment of globals to enable forceTemporaries',
|
'Expected reassignment of globals to enable forceTemporaries',
|
||||||
loc: property.node.loc ?? GeneratedSource,
|
loc: property.node.loc ?? GeneratedSource,
|
||||||
|
|
@ -4035,6 +4105,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
|
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: property.node.loc ?? null,
|
loc: property.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -4044,6 +4115,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
|
reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: property.node.loc ?? null,
|
loc: property.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -4058,6 +4130,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
|
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: element.node.loc ?? null,
|
loc: element.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -4080,6 +4153,7 @@ function lowerAssignment(
|
||||||
} else if (identifier.kind === 'Global') {
|
} else if (identifier.kind === 'Global') {
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason:
|
reason:
|
||||||
'Expected reassignment of globals to enable forceTemporaries',
|
'Expected reassignment of globals to enable forceTemporaries',
|
||||||
loc: element.node.loc ?? GeneratedSource,
|
loc: element.node.loc ?? GeneratedSource,
|
||||||
|
|
@ -4229,6 +4303,7 @@ function lowerAssignment(
|
||||||
builder.errors.push({
|
builder.errors.push({
|
||||||
reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
|
reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: lvaluePath.node.loc ?? null,
|
loc: lvaluePath.node.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import {Binding, NodePath} from '@babel/traverse';
|
import {Binding, NodePath} from '@babel/traverse';
|
||||||
import * as t from '@babel/types';
|
import * as t from '@babel/types';
|
||||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||||
import {Environment} from './Environment';
|
import {Environment} from './Environment';
|
||||||
import {
|
import {
|
||||||
BasicBlock,
|
BasicBlock,
|
||||||
|
|
@ -310,7 +310,8 @@ export default class HIRBuilder {
|
||||||
if (node.name === 'fbt') {
|
if (node.name === 'fbt') {
|
||||||
CompilerError.throwDiagnostic({
|
CompilerError.throwDiagnostic({
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
category: 'Support local variables named `fbt`',
|
category: ErrorCategory.FBT,
|
||||||
|
reason: 'Support local variables named `fbt`',
|
||||||
description:
|
description:
|
||||||
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
|
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
|
||||||
details: [
|
details: [
|
||||||
|
|
|
||||||
|
|
@ -986,13 +986,13 @@ export function printAliasingEffect(effect: AliasingEffect): string {
|
||||||
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
|
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
|
||||||
}
|
}
|
||||||
case 'MutateFrozen': {
|
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': {
|
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': {
|
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': {
|
case 'Render': {
|
||||||
return `Render ${printPlaceForAliasEffect(effect.place)}`;
|
return `Render ${printPlaceForAliasEffect(effect.place)}`;
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ export function hashEffect(effect: AliasingEffect): string {
|
||||||
effect.kind,
|
effect.kind,
|
||||||
effect.place.identifier.id,
|
effect.place.identifier.id,
|
||||||
effect.error.severity,
|
effect.error.severity,
|
||||||
effect.error.category,
|
effect.error.reason,
|
||||||
effect.error.description,
|
effect.error.description,
|
||||||
printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource),
|
printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource),
|
||||||
].join(':');
|
].join(':');
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
} from '..';
|
} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
Effect,
|
Effect,
|
||||||
|
|
@ -300,8 +301,9 @@ function extractManualMemoizationArgs(
|
||||||
if (fnPlace == null) {
|
if (fnPlace == null) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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}`,
|
description: `Expected a callback function to be passed to ${kind}`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
@ -315,8 +317,9 @@ function extractManualMemoizationArgs(
|
||||||
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
|
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: `Unexpected spread argument to ${kind}`,
|
reason: `Unexpected spread argument to ${kind}`,
|
||||||
description: `Unexpected spread argument to ${kind}`,
|
description: `Unexpected spread argument to ${kind}`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
@ -335,8 +338,9 @@ function extractManualMemoizationArgs(
|
||||||
if (maybeDepsList == null) {
|
if (maybeDepsList == null) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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`,
|
description: `Expected the dependency list for ${kind} to be an array literal`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
@ -353,8 +357,9 @@ function extractManualMemoizationArgs(
|
||||||
if (maybeDep == null) {
|
if (maybeDep == null) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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\`)`,
|
description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
@ -459,7 +464,8 @@ export function dropManualMemoization(
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'useMemo() callbacks must return a value',
|
category: ErrorCategory.UseMemo,
|
||||||
|
reason: 'useMemo() callbacks must return a value',
|
||||||
description: `This ${
|
description: `This ${
|
||||||
manualMemo.loadInstr.value.kind === 'PropertyLoad'
|
manualMemo.loadInstr.value.kind === 'PropertyLoad'
|
||||||
? 'React.useMemo'
|
? 'React.useMemo'
|
||||||
|
|
@ -498,8 +504,9 @@ export function dropManualMemoization(
|
||||||
if (!sidemap.functions.has(fnPlace.identifier.id)) {
|
if (!sidemap.functions.has(fnPlace.identifier.id)) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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`,
|
description: `Expected the first argument to be an inline function expression`,
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import {
|
||||||
hashEffect,
|
hashEffect,
|
||||||
MutationReason,
|
MutationReason,
|
||||||
} from './AliasingEffects';
|
} from './AliasingEffects';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
|
|
||||||
|
|
@ -452,8 +453,9 @@ function applySignature(
|
||||||
? `\`${effect.value.identifier.name.value}\``
|
? `\`${effect.value.identifier.name.value}\``
|
||||||
: 'value';
|
: 'value';
|
||||||
const diagnostic = CompilerDiagnostic.create({
|
const diagnostic = CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'This value cannot be modified',
|
reason: 'This value cannot be modified',
|
||||||
description: `${reason}.`,
|
description: `${reason}.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -1036,8 +1038,9 @@ function applyEffect(
|
||||||
effect.value.identifier.declarationId,
|
effect.value.identifier.declarationId,
|
||||||
);
|
);
|
||||||
const diagnostic = CompilerDiagnostic.create({
|
const diagnostic = CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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.`,
|
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) {
|
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
|
||||||
|
|
@ -1075,8 +1078,9 @@ function applyEffect(
|
||||||
? `\`${effect.value.identifier.name.value}\``
|
? `\`${effect.value.identifier.name.value}\``
|
||||||
: 'value';
|
: 'value';
|
||||||
const diagnostic = CompilerDiagnostic.create({
|
const diagnostic = CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'This value cannot be modified',
|
reason: 'This value cannot be modified',
|
||||||
description: `${reason}.`,
|
description: `${reason}.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -2033,8 +2037,9 @@ function computeSignatureForInstruction(
|
||||||
kind: 'MutateGlobal',
|
kind: 'MutateGlobal',
|
||||||
place: value.value,
|
place: value.value,
|
||||||
error: CompilerDiagnostic.create({
|
error: CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Globals,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category:
|
reason:
|
||||||
'Cannot reassign variables declared outside of the component/hook',
|
'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)`,
|
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({
|
}).withDetail({
|
||||||
|
|
@ -2132,8 +2137,9 @@ function computeEffectsForLegacySignature(
|
||||||
kind: 'Impure',
|
kind: 'Impure',
|
||||||
place: receiver,
|
place: receiver,
|
||||||
error: CompilerDiagnostic.create({
|
error: CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Purity,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot call impure function during render',
|
reason: 'Cannot call impure function during render',
|
||||||
description:
|
description:
|
||||||
(signature.canonicalName != null
|
(signature.canonicalName != null
|
||||||
? `\`${signature.canonicalName}\` is an impure function. `
|
? `\`${signature.canonicalName}\` is an impure function. `
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import {
|
||||||
mapInstructionValueOperands,
|
mapInstructionValueOperands,
|
||||||
mapTerminalOperands,
|
mapTerminalOperands,
|
||||||
} from '../HIR/visitors';
|
} from '../HIR/visitors';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
|
|
||||||
type InlinedJsxDeclarationMap = Map<
|
type InlinedJsxDeclarationMap = Map<
|
||||||
DeclarationId,
|
DeclarationId,
|
||||||
|
|
@ -83,6 +84,7 @@ export function inlineJsxTransform(
|
||||||
kind: 'CompileDiagnostic',
|
kind: 'CompileDiagnostic',
|
||||||
fnLoc: null,
|
fnLoc: null,
|
||||||
detail: {
|
detail: {
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
reason: 'JSX Inlining is not supported on value blocks',
|
reason: 'JSX Inlining is not supported on value blocks',
|
||||||
loc: instr.loc,
|
loc: instr.loc,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
pruneUnusedLabels,
|
pruneUnusedLabels,
|
||||||
renameVariables,
|
renameVariables,
|
||||||
} from '.';
|
} from '.';
|
||||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||||
import {Environment, ExternalFunction} from '../HIR';
|
import {Environment, ExternalFunction} from '../HIR';
|
||||||
import {
|
import {
|
||||||
ArrayPattern,
|
ArrayPattern,
|
||||||
|
|
@ -2185,6 +2185,7 @@ function codegenInstructionValue(
|
||||||
(declarator.id as t.Identifier).name
|
(declarator.id as t.Identifier).name
|
||||||
}'`,
|
}'`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: declarator.loc ?? null,
|
loc: declarator.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -2193,6 +2194,7 @@ function codegenInstructionValue(
|
||||||
cx.errors.push({
|
cx.errors.push({
|
||||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
|
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
|
||||||
severity: ErrorSeverity.Todo,
|
severity: ErrorSeverity.Todo,
|
||||||
|
category: ErrorCategory.Todo,
|
||||||
loc: stmt.loc ?? null,
|
loc: stmt.loc ?? null,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import {
|
||||||
import {eachInstructionOperand} from '../HIR/visitors';
|
import {eachInstructionOperand} from '../HIR/visitors';
|
||||||
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
||||||
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
|
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO(jmbrown):
|
* TODO(jmbrown):
|
||||||
|
|
@ -133,6 +134,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
loc: value.loc,
|
loc: value.loc,
|
||||||
description: null,
|
description: null,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
reason: '[InsertFire] No LoadGlobal found for useEffect call',
|
reason: '[InsertFire] No LoadGlobal found for useEffect call',
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -179,6 +181,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
description:
|
description:
|
||||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -189,6 +192,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
description:
|
description:
|
||||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -223,6 +227,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
loc: value.loc,
|
loc: value.loc,
|
||||||
description: null,
|
description: null,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
|
category: ErrorCategory.Invariant,
|
||||||
reason:
|
reason:
|
||||||
'[InsertFire] No loadLocal found for fire call argument',
|
'[InsertFire] No loadLocal found for fire call argument',
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
|
|
@ -246,6 +251,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
description:
|
description:
|
||||||
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
|
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -264,6 +270,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||||
loc: value.loc,
|
loc: value.loc,
|
||||||
description,
|
description,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
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 \
|
this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \
|
||||||
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
|
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
});
|
});
|
||||||
|
|
@ -411,6 +419,7 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
|
||||||
context.pushError({
|
context.pushError({
|
||||||
loc: place.identifier.loc,
|
loc: place.identifier.loc,
|
||||||
description: 'Cannot use `fire` outside of a useEffect function',
|
description: 'Cannot use `fire` outside of a useEffect function',
|
||||||
|
category: ErrorCategory.Fire,
|
||||||
severity: ErrorSeverity.Invariant,
|
severity: ErrorSeverity.Invariant,
|
||||||
reason: CANNOT_COMPILE_FIRE,
|
reason: CANNOT_COMPILE_FIRE,
|
||||||
suggestions: null,
|
suggestions: null,
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,13 @@ export function Ok<T>(val: T): OkImpl<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class OkImpl<T> implements Result<T, never> {
|
class OkImpl<T> implements Result<T, never> {
|
||||||
constructor(private val: T) {}
|
#val: T;
|
||||||
|
constructor(val: T) {
|
||||||
|
this.#val = val;
|
||||||
|
}
|
||||||
|
|
||||||
map<U>(fn: (val: T) => U): Result<U, never> {
|
map<U>(fn: (val: T) => U): Result<U, never> {
|
||||||
return new OkImpl(fn(this.val));
|
return new OkImpl(fn(this.#val));
|
||||||
}
|
}
|
||||||
|
|
||||||
mapErr<F>(_fn: (val: never) => F): Result<T, F> {
|
mapErr<F>(_fn: (val: never) => F): Result<T, F> {
|
||||||
|
|
@ -101,15 +104,15 @@ class OkImpl<T> implements Result<T, never> {
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOr<U>(_fallback: U, fn: (val: T) => U): U {
|
mapOr<U>(_fallback: U, fn: (val: T) => U): U {
|
||||||
return fn(this.val);
|
return fn(this.#val);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOrElse<U>(_fallback: () => U, fn: (val: T) => U): U {
|
mapOrElse<U>(_fallback: () => U, fn: (val: T) => U): U {
|
||||||
return fn(this.val);
|
return fn(this.#val);
|
||||||
}
|
}
|
||||||
|
|
||||||
andThen<U>(fn: (val: T) => Result<U, never>): Result<U, never> {
|
andThen<U>(fn: (val: T) => Result<U, never>): Result<U, never> {
|
||||||
return fn(this.val);
|
return fn(this.#val);
|
||||||
}
|
}
|
||||||
|
|
||||||
and<U>(res: Result<U, never>): Result<U, never> {
|
and<U>(res: Result<U, never>): Result<U, never> {
|
||||||
|
|
@ -133,30 +136,30 @@ class OkImpl<T> implements Result<T, never> {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(_msg: string): T {
|
expect(_msg: string): T {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
|
|
||||||
expectErr(msg: string): never {
|
expectErr(msg: string): never {
|
||||||
throw new Error(`${msg}: ${this.val}`);
|
throw new Error(`${msg}: ${this.#val}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrap(): T {
|
unwrap(): T {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapOr(_fallback: T): T {
|
unwrapOr(_fallback: T): T {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapOrElse(_fallback: (val: never) => T): T {
|
unwrapOrElse(_fallback: (val: never) => T): T {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapErr(): never {
|
unwrapErr(): never {
|
||||||
if (this.val instanceof Error) {
|
if (this.#val instanceof Error) {
|
||||||
throw this.val;
|
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<E>(val: E): ErrImpl<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrImpl<E> implements Result<never, E> {
|
class ErrImpl<E> implements Result<never, E> {
|
||||||
constructor(private val: E) {}
|
#val: E;
|
||||||
|
constructor(val: E) {
|
||||||
|
this.#val = val;
|
||||||
|
}
|
||||||
|
|
||||||
map<U>(_fn: (val: never) => U): Result<U, E> {
|
map<U>(_fn: (val: never) => U): Result<U, E> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapErr<F>(fn: (val: E) => F): Result<never, F> {
|
mapErr<F>(fn: (val: E) => F): Result<never, F> {
|
||||||
return new ErrImpl(fn(this.val));
|
return new ErrImpl(fn(this.#val));
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOr<U>(fallback: U, _fn: (val: never) => U): U {
|
mapOr<U>(fallback: U, _fn: (val: never) => U): U {
|
||||||
|
|
@ -196,7 +202,7 @@ class ErrImpl<E> implements Result<never, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
orElse<F>(fn: (val: E) => ErrImpl<F>): Result<never, F> {
|
orElse<F>(fn: (val: E) => ErrImpl<F>): Result<never, F> {
|
||||||
return fn(this.val);
|
return fn(this.#val);
|
||||||
}
|
}
|
||||||
|
|
||||||
isOk(): this is OkImpl<never> {
|
isOk(): this is OkImpl<never> {
|
||||||
|
|
@ -208,18 +214,18 @@ class ErrImpl<E> implements Result<never, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(msg: string): never {
|
expect(msg: string): never {
|
||||||
throw new Error(`${msg}: ${this.val}`);
|
throw new Error(`${msg}: ${this.#val}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectErr(_msg: string): E {
|
expectErr(_msg: string): E {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrap(): never {
|
unwrap(): never {
|
||||||
if (this.val instanceof Error) {
|
if (this.#val instanceof Error) {
|
||||||
throw this.val;
|
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<T>(fallback: T): T {
|
unwrapOr<T>(fallback: T): T {
|
||||||
|
|
@ -227,10 +233,10 @@ class ErrImpl<E> implements Result<never, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapOrElse<T>(fallback: (val: E) => T): T {
|
unwrapOrElse<T>(fallback: (val: E) => T): T {
|
||||||
return fallback(this.val);
|
return fallback(this.#val);
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapErr(): E {
|
unwrapErr(): E {
|
||||||
return this.val;
|
return this.#val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import * as t from '@babel/types';
|
||||||
import {
|
import {
|
||||||
CompilerError,
|
CompilerError,
|
||||||
CompilerErrorDetail,
|
CompilerErrorDetail,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
||||||
|
|
@ -124,6 +125,7 @@ export function validateHooksUsage(
|
||||||
recordError(
|
recordError(
|
||||||
place.loc,
|
place.loc,
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
|
category: ErrorCategory.Hooks,
|
||||||
description: null,
|
description: null,
|
||||||
reason,
|
reason,
|
||||||
loc: place.loc,
|
loc: place.loc,
|
||||||
|
|
@ -140,6 +142,7 @@ export function validateHooksUsage(
|
||||||
recordError(
|
recordError(
|
||||||
place.loc,
|
place.loc,
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
|
category: ErrorCategory.Hooks,
|
||||||
description: null,
|
description: null,
|
||||||
reason:
|
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',
|
'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(
|
recordError(
|
||||||
place.loc,
|
place.loc,
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
|
category: ErrorCategory.Hooks,
|
||||||
description: null,
|
description: null,
|
||||||
reason:
|
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',
|
'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) {
|
for (const [, error] of errorsByPlace) {
|
||||||
errors.push(error);
|
errors.pushErrorDetail(error);
|
||||||
}
|
}
|
||||||
return errors.asResult();
|
return errors.asResult();
|
||||||
}
|
}
|
||||||
|
|
@ -448,6 +452,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||||
if (hookKind != null) {
|
if (hookKind != null) {
|
||||||
errors.pushErrorDetail(
|
errors.pushErrorDetail(
|
||||||
new CompilerErrorDetail({
|
new CompilerErrorDetail({
|
||||||
|
category: ErrorCategory.Hooks,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
reason:
|
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)',
|
'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)',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {HIRFunction, IdentifierId, Place} from '../HIR';
|
import {HIRFunction, IdentifierId, Place} from '../HIR';
|
||||||
import {
|
import {
|
||||||
eachInstructionLValue,
|
eachInstructionLValue,
|
||||||
|
|
@ -36,8 +37,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
|
||||||
: 'variable';
|
: 'variable';
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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.`,
|
description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -91,8 +93,9 @@ function getContextReassignment(
|
||||||
: 'variable';
|
: 'variable';
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot reassign variable in async function',
|
reason: 'Cannot reassign variable in async function',
|
||||||
description:
|
description:
|
||||||
'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead',
|
'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead',
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerError, ErrorSeverity} from '..';
|
import {CompilerError, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
Identifier,
|
Identifier,
|
||||||
Instruction,
|
Instruction,
|
||||||
|
|
@ -108,6 +109,7 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
|
||||||
isUnmemoized(deps.identifier, this.scopes))
|
isUnmemoized(deps.identifier, this.scopes))
|
||||||
) {
|
) {
|
||||||
state.push({
|
state.push({
|
||||||
|
category: ErrorCategory.EffectDependencies,
|
||||||
reason:
|
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',
|
'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,
|
description: null,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
|
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {HIRFunction, IdentifierId} from '../HIR';
|
import {HIRFunction, IdentifierId} from '../HIR';
|
||||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||||
import {Result} from '../Utils/Result';
|
import {Result} from '../Utils/Result';
|
||||||
|
|
@ -56,6 +57,7 @@ export function validateNoCapitalizedCalls(
|
||||||
const calleeName = capitalLoadGlobals.get(calleeIdentifier);
|
const calleeName = capitalLoadGlobals.get(calleeIdentifier);
|
||||||
if (calleeName != null) {
|
if (calleeName != null) {
|
||||||
CompilerError.throwInvalidReact({
|
CompilerError.throwInvalidReact({
|
||||||
|
category: ErrorCategory.CapitalizedCalls,
|
||||||
reason,
|
reason,
|
||||||
description: `${calleeName} may be a component.`,
|
description: `${calleeName} may be a component.`,
|
||||||
loc: value.loc,
|
loc: value.loc,
|
||||||
|
|
@ -79,6 +81,7 @@ export function validateNoCapitalizedCalls(
|
||||||
const propertyName = capitalizedProperties.get(propertyIdentifier);
|
const propertyName = capitalizedProperties.get(propertyIdentifier);
|
||||||
if (propertyName != null) {
|
if (propertyName != null) {
|
||||||
errors.push({
|
errors.push({
|
||||||
|
category: ErrorCategory.CapitalizedCalls,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
reason,
|
reason,
|
||||||
description: `${propertyName} may be a component.`,
|
description: `${propertyName} may be a component.`,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
|
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
BlockId,
|
BlockId,
|
||||||
|
|
@ -219,6 +220,7 @@ function validateEffect(
|
||||||
|
|
||||||
for (const loc of setStateLocations) {
|
for (const loc of setStateLocations) {
|
||||||
errors.push({
|
errors.push({
|
||||||
|
category: ErrorCategory.EffectDerivationsOfState,
|
||||||
reason:
|
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)',
|
'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,
|
description: null,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
HIRFunction,
|
HIRFunction,
|
||||||
IdentifierId,
|
IdentifierId,
|
||||||
|
|
@ -64,8 +65,9 @@ export function validateNoFreezingKnownMutableFunctions(
|
||||||
: 'a local variable';
|
: 'a local variable';
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Immutability,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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.`,
|
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({
|
.withDetail({
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {HIRFunction} from '../HIR';
|
import {HIRFunction} from '../HIR';
|
||||||
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
|
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
|
||||||
import {Result} from '../Utils/Result';
|
import {Result} from '../Utils/Result';
|
||||||
|
|
@ -36,7 +37,8 @@ export function validateNoImpureFunctionsInRender(
|
||||||
if (signature != null && signature.impure === true) {
|
if (signature != null && signature.impure === true) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
category: 'Cannot call impure function during render',
|
category: ErrorCategory.Purity,
|
||||||
|
reason: 'Cannot call impure function during render',
|
||||||
description:
|
description:
|
||||||
(signature.canonicalName != null
|
(signature.canonicalName != null
|
||||||
? `\`${signature.canonicalName}\` is an impure function. `
|
? `\`${signature.canonicalName}\` is an impure function. `
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
||||||
|
import {ErrorCategory} from '../CompilerError';
|
||||||
import {BlockId, HIRFunction} from '../HIR';
|
import {BlockId, HIRFunction} from '../HIR';
|
||||||
import {Result} from '../Utils/Result';
|
import {Result} from '../Utils/Result';
|
||||||
import {retainWhere} from '../Utils/utils';
|
import {retainWhere} from '../Utils/utils';
|
||||||
|
|
@ -36,8 +37,9 @@ export function validateNoJSXInTryStatement(
|
||||||
case 'JsxFragment': {
|
case 'JsxFragment': {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.ErrorBoundaries,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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)`,
|
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({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
|
|
@ -468,8 +469,9 @@ function validateNoRefAccessInRenderImpl(
|
||||||
didError = true;
|
didError = true;
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -731,8 +733,9 @@ function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
|
||||||
if (env.get(operand.identifier.id)?.kind === 'Guard') {
|
if (env.get(operand.identifier.id)?.kind === 'Guard') {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -755,8 +758,9 @@ function validateNoRefValueAccess(
|
||||||
) {
|
) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -781,8 +785,9 @@ function validateNoRefPassedToFunction(
|
||||||
) {
|
) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -803,8 +808,9 @@ function validateNoRefUpdate(
|
||||||
if (type?.kind === 'Ref' || type?.kind === 'RefValue') {
|
if (type?.kind === 'Ref' || type?.kind === 'RefValue') {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
@ -824,8 +830,9 @@ function validateNoDirectRefValueAccess(
|
||||||
if (type?.kind === 'RefValue') {
|
if (type?.kind === 'RefValue') {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.Refs,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'Cannot access refs during render',
|
reason: 'Cannot access refs during render',
|
||||||
description: ERROR_DESCRIPTION,
|
description: ERROR_DESCRIPTION,
|
||||||
}).withDetail({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
|
|
@ -96,7 +97,8 @@ export function validateNoSetStateInEffects(
|
||||||
if (setState !== undefined) {
|
if (setState !== undefined) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
category:
|
category: ErrorCategory.EffectSetState,
|
||||||
|
reason:
|
||||||
'Calling setState synchronously within an effect can trigger cascading renders',
|
'Calling setState synchronously within an effect can trigger cascading renders',
|
||||||
description:
|
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. ' +
|
'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
|
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
|
||||||
|
|
@ -128,7 +129,8 @@ function validateNoSetStateInRenderImpl(
|
||||||
if (activeManualMemoId !== null) {
|
if (activeManualMemoId !== null) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
category:
|
category: ErrorCategory.RenderSetState,
|
||||||
|
reason:
|
||||||
'Calling setState from useMemo may trigger an infinite loop',
|
'Calling setState from useMemo may trigger an infinite loop',
|
||||||
description:
|
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)',
|
'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)) {
|
} else if (unconditionalBlocks.has(block.id)) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
category:
|
category: ErrorCategory.RenderSetState,
|
||||||
|
reason:
|
||||||
'Calling setState during render may trigger an infinite loop',
|
'Calling setState during render may trigger an infinite loop',
|
||||||
description:
|
description:
|
||||||
'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
|
'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({
|
}).withDetail({
|
||||||
kind: 'error',
|
kind: 'error',
|
||||||
loc: callee.loc,
|
loc: callee.loc,
|
||||||
message: 'Found setState() within useMemo()',
|
message: 'Found setState() in render',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {
|
import {
|
||||||
|
|
@ -281,8 +282,9 @@ function validateInferredDep(
|
||||||
}
|
}
|
||||||
errorState.pushDiagnostic(
|
errorState.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.PreserveManualMemo,
|
||||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||||
category:
|
reason:
|
||||||
'Compilation skipped because existing memoization could not be preserved',
|
'Compilation skipped because existing memoization could not be preserved',
|
||||||
description: [
|
description: [
|
||||||
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
||||||
|
|
@ -535,8 +537,9 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||||
) {
|
) {
|
||||||
state.errors.pushDiagnostic(
|
state.errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.PreserveManualMemo,
|
||||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||||
category:
|
reason:
|
||||||
'Compilation skipped because existing memoization could not be preserved',
|
'Compilation skipped because existing memoization could not be preserved',
|
||||||
description: [
|
description: [
|
||||||
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
||||||
|
|
@ -583,8 +586,9 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||||
if (isUnmemoized(identifier, this.scopes)) {
|
if (isUnmemoized(identifier, this.scopes)) {
|
||||||
state.errors.pushDiagnostic(
|
state.errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.PreserveManualMemo,
|
||||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||||
category:
|
reason:
|
||||||
'Compilation skipped because existing memoization could not be preserved',
|
'Compilation skipped because existing memoization could not be preserved',
|
||||||
description: [
|
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. ',
|
'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. ',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
|
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
|
||||||
|
|
@ -65,8 +66,9 @@ export function validateStaticComponents(
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
error.pushDiagnostic(
|
error.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.StaticComponents,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
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. `,
|
description: `Components created during render will reset their state each time they are created. Declare components outside of render. `,
|
||||||
})
|
})
|
||||||
.withDetail({
|
.withDetail({
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import {
|
import {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
|
ErrorCategory,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
} from '../CompilerError';
|
} from '../CompilerError';
|
||||||
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
|
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
|
||||||
|
|
@ -74,8 +75,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||||
: firstParam.place.loc;
|
: firstParam.place.loc;
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category: 'useMemo() callbacks may not accept parameters',
|
reason: 'useMemo() callbacks may not accept parameters',
|
||||||
description:
|
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.',
|
'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,
|
suggestions: null,
|
||||||
|
|
@ -90,8 +92,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||||
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
|
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
|
||||||
errors.pushDiagnostic(
|
errors.pushDiagnostic(
|
||||||
CompilerDiagnostic.create({
|
CompilerDiagnostic.create({
|
||||||
|
category: ErrorCategory.UseMemo,
|
||||||
severity: ErrorSeverity.InvalidReact,
|
severity: ErrorSeverity.InvalidReact,
|
||||||
category:
|
reason:
|
||||||
'useMemo() callbacks may not be async or generator functions',
|
'useMemo() callbacks may not be async or generator functions',
|
||||||
description:
|
description:
|
||||||
'useMemo() callbacks are called once and must synchronously return a value.',
|
'useMemo() callbacks are called once and must synchronously return a value.',
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ error.invalid-unconditional-set-state-in-render.ts:6:2
|
||||||
4 | const aliased = setX;
|
4 | const aliased = setX;
|
||||||
5 |
|
5 |
|
||||||
> 6 | setX(1);
|
> 6 | setX(1);
|
||||||
| ^^^^ Found setState() within useMemo()
|
| ^^^^ Found setState() in render
|
||||||
7 | aliased(2);
|
7 | aliased(2);
|
||||||
8 |
|
8 |
|
||||||
9 | return x;
|
9 | return x;
|
||||||
|
|
@ -42,7 +42,7 @@ error.invalid-unconditional-set-state-in-render.ts:7:2
|
||||||
5 |
|
5 |
|
||||||
6 | setX(1);
|
6 | setX(1);
|
||||||
> 7 | aliased(2);
|
> 7 | aliased(2);
|
||||||
| ^^^^^^^ Found setState() within useMemo()
|
| ^^^^^^^ Found setState() in render
|
||||||
8 |
|
8 |
|
||||||
9 | return x;
|
9 | return x;
|
||||||
10 | }
|
10 | }
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-after-loop-break.ts:11:2
|
||||||
9 | }
|
9 | }
|
||||||
10 | }
|
10 | }
|
||||||
> 11 | setState(true);
|
> 11 | setState(true);
|
||||||
| ^^^^^^^^ Found setState() within useMemo()
|
| ^^^^^^^^ Found setState() in render
|
||||||
12 | return state;
|
12 | return state;
|
||||||
13 | }
|
13 | }
|
||||||
14 |
|
14 |
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ error.unconditional-set-state-in-render-after-loop.ts:6:2
|
||||||
4 | for (const _ of props) {
|
4 | for (const _ of props) {
|
||||||
5 | }
|
5 | }
|
||||||
> 6 | setState(true);
|
> 6 | setState(true);
|
||||||
| ^^^^^^^^ Found setState() within useMemo()
|
| ^^^^^^^^ Found setState() in render
|
||||||
7 | return state;
|
7 | return state;
|
||||||
8 | }
|
8 | }
|
||||||
9 |
|
9 |
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ error.unconditional-set-state-in-render-with-loop-throw.ts:11:2
|
||||||
9 | }
|
9 | }
|
||||||
10 | }
|
10 | }
|
||||||
> 11 | setState(true);
|
> 11 | setState(true);
|
||||||
| ^^^^^^^^ Found setState() within useMemo()
|
| ^^^^^^^^ Found setState() in render
|
||||||
12 | return state;
|
12 | return state;
|
||||||
13 | }
|
13 | }
|
||||||
14 |
|
14 |
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ error.unconditional-set-state-lambda.ts:8:2
|
||||||
6 | setX(1);
|
6 | setX(1);
|
||||||
7 | };
|
7 | };
|
||||||
> 8 | foo();
|
> 8 | foo();
|
||||||
| ^^^ Found setState() within useMemo()
|
| ^^^ Found setState() in render
|
||||||
9 |
|
9 |
|
||||||
10 | return [x];
|
10 | return [x];
|
||||||
11 | }
|
11 | }
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ error.unconditional-set-state-nested-function-expressions.ts:16:2
|
||||||
14 | bar();
|
14 | bar();
|
||||||
15 | };
|
15 | };
|
||||||
> 16 | baz();
|
> 16 | baz();
|
||||||
| ^^^ Found setState() within useMemo()
|
| ^^^ Found setState() in render
|
||||||
17 |
|
17 |
|
||||||
18 | return [x];
|
18 | return [x];
|
||||||
19 | }
|
19 | }
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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
|
### Eval output
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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
|
### Eval output
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":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":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
|
||||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function Component(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ function Component(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function _temp(s) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ function _temp(s) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":"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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||||
## Logs
|
## 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":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":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":348},"end":{"line":11,"column":54,"index":400},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":371},"end":{"line":11,"column":29,"index":375},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":381},"end":{"line":11,"column":42,"index":388},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
|
||||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":227},"end":{"line":14,"column":1,"index":441},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ function Example(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ function Example(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ function Example(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ function Example(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ function Example(props) {
|
||||||
## Logs
|
## 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}
|
{"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}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,11 @@ export {
|
||||||
CompilerDiagnostic,
|
CompilerDiagnostic,
|
||||||
CompilerSuggestionOperation,
|
CompilerSuggestionOperation,
|
||||||
ErrorSeverity,
|
ErrorSeverity,
|
||||||
|
LintRules,
|
||||||
type CompilerErrorDetailOptions,
|
type CompilerErrorDetailOptions,
|
||||||
type CompilerDiagnosticOptions,
|
type CompilerDiagnosticOptions,
|
||||||
type CompilerDiagnosticDetail,
|
type CompilerDiagnosticDetail,
|
||||||
|
type LintRule,
|
||||||
} from './CompilerError';
|
} from './CompilerError';
|
||||||
export {
|
export {
|
||||||
compileFn as compile,
|
compileFn as compile,
|
||||||
|
|
|
||||||
|
|
@ -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 <Foo date={date} now={now} rand={rand} />;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
makeTestCaseError('Cannot call impure function during render'),
|
||||||
|
makeTestCaseError('Cannot call impure function during render'),
|
||||||
|
makeTestCaseError('Cannot call impure function during render'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -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 <div>Hello world</div>;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 <div>Hello world</div>;
|
||||||
|
}`,
|
||||||
|
errors: [
|
||||||
|
makeTestCaseError(
|
||||||
|
'Hooks must always be called in a consistent order',
|
||||||
|
),
|
||||||
|
makeTestCaseError(
|
||||||
|
'Hooks must always be called in a consistent order',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -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 = <Child />;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [makeTestCaseError('Avoid constructing JSX within try/catch')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -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')],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -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 <div>Hello world</div>
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: "Unused 'use no forget' directive",
|
||||||
|
suggestions: [
|
||||||
|
{
|
||||||
|
output:
|
||||||
|
// yuck
|
||||||
|
'\nfunction Component() {\n \n return <div>Hello world</div>\n}\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks",
|
||||||
|
code: normalizeIndent`
|
||||||
|
function notacomponent() {
|
||||||
|
'use no forget';
|
||||||
|
return 1 + 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: "Unused 'use no forget' directive",
|
||||||
|
suggestions: [
|
||||||
|
{
|
||||||
|
output:
|
||||||
|
// yuck
|
||||||
|
'\nfunction notacomponent() {\n \n return 1 + 1;\n}\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
@ -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 <div onClick={onClick}>{text}</div>;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// 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 <Child x={state} />;
|
||||||
|
}`,
|
||||||
|
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 <div>Hello world</div>;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
environment: {
|
||||||
|
inferEffectDependencies: [
|
||||||
|
{
|
||||||
|
function: {
|
||||||
|
source: 'useMyEffect',
|
||||||
|
importSpecifierName: 'default',
|
||||||
|
},
|
||||||
|
autodepsIndex: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: /Cannot infer dependencies of this effect/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
@ -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 <div onClick={onClick}>{text}</div>;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}`,
|
|
||||||
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 <div>{props.foo}</div>;
|
|
||||||
}`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: /React Compiler has skipped optimizing this component/,
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output: normalizeIndent`
|
|
||||||
function Component(props) {
|
|
||||||
|
|
||||||
return <div>{props.foo}</div>;
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}
|
|
||||||
function Bar(props) {
|
|
||||||
props.a.b = 2;
|
|
||||||
return <div>{props.c}</div>
|
|
||||||
}`,
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}`,
|
|
||||||
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 <div>Hello world {count}</div>
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
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 <div>Hello world</div>
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: "Unused 'use no forget' directive",
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output:
|
|
||||||
// yuck
|
|
||||||
'\nfunction Component() {\n \n return <div>Hello world</div>\n}\n',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks",
|
|
||||||
code: normalizeIndent`
|
|
||||||
function notacomponent() {
|
|
||||||
'use no forget';
|
|
||||||
return 1 + 1;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: "Unused 'use no forget' directive",
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output:
|
|
||||||
// yuck
|
|
||||||
'\nfunction notacomponent() {\n \n return 1 + 1;\n}\n',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>Hello world</div>;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
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);
|
|
||||||
|
|
@ -6,22 +6,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RuleTester} from 'eslint';
|
import {RuleTester} from 'eslint';
|
||||||
import ReactCompilerRule from '../src/rules/ReactCompilerRule';
|
import {
|
||||||
|
CompilerTestCases,
|
||||||
/**
|
normalizeIndent,
|
||||||
* A string template tag that removes padding from the left side of multi-line strings
|
TestRecommendedRules,
|
||||||
* @param {Array} strings array of code strings (only one expected)
|
} from './shared-utils';
|
||||||
*/
|
|
||||||
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[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const tests: CompilerTestCases = {
|
const tests: CompilerTestCases = {
|
||||||
valid: [
|
valid: [
|
||||||
|
|
@ -70,6 +59,7 @@ const tests: CompilerTestCases = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const eslintTester = new RuleTester({
|
const eslintTester = new RuleTester({
|
||||||
|
// @ts-ignore[2353] - outdated types
|
||||||
parser: require.resolve('@typescript-eslint/parser'),
|
parser: require.resolve('@typescript-eslint/parser'),
|
||||||
});
|
});
|
||||||
eslintTester.run('react-compiler', ReactCompilerRule, tests);
|
eslintTester.run('react-compiler', TestRecommendedRules, tests);
|
||||||
|
|
|
||||||
|
|
@ -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', () => {});
|
||||||
|
|
@ -24,11 +24,13 @@
|
||||||
"@babel/preset-typescript": "^7.18.6",
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
"@babel/types": "^7.26.0",
|
"@babel/types": "^7.26.0",
|
||||||
"@types/eslint": "^8.56.12",
|
"@types/eslint": "^8.56.12",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.2.5",
|
||||||
"babel-jest": "^29.0.3",
|
"babel-jest": "^29.0.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"hermes-eslint": "^0.25.1",
|
"hermes-eslint": "^0.25.1",
|
||||||
"jest": "^29.5.0"
|
"jest": "^29.5.0",
|
||||||
|
"regexp.escape": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
|
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,26 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ReactCompilerRule from './rules/ReactCompilerRule';
|
import {allRules, recommendedRules} from './rules/ReactCompilerRule';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
name: 'eslint-plugin-react-compiler',
|
name: 'eslint-plugin-react-compiler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const rules = {
|
|
||||||
'react-compiler': ReactCompilerRule,
|
|
||||||
};
|
|
||||||
|
|
||||||
const configs = {
|
const configs = {
|
||||||
recommended: {
|
recommended: {
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-compiler': {
|
'react-compiler': {
|
||||||
rules: {
|
rules: allRules,
|
||||||
'react-compiler': ReactCompilerRule,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
rules: Object.fromEntries(
|
||||||
rules: {
|
Object.keys(recommendedRules).map(ruleName => [
|
||||||
'react-compiler/react-compiler': 'error' as const,
|
'react-compiler/' + ruleName,
|
||||||
},
|
'error',
|
||||||
|
]),
|
||||||
|
) as Record<string, 'error' | 'warn'>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export {configs, rules, meta};
|
export {configs, allRules as rules, meta};
|
||||||
|
|
|
||||||
|
|
@ -5,43 +5,23 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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 type {SourceLocation as BabelSourceLocation} from '@babel/types';
|
||||||
import BabelPluginReactCompiler, {
|
import {
|
||||||
CompilerDiagnostic,
|
|
||||||
CompilerDiagnosticOptions,
|
CompilerDiagnosticOptions,
|
||||||
CompilerErrorDetail,
|
|
||||||
CompilerErrorDetailOptions,
|
CompilerErrorDetailOptions,
|
||||||
CompilerSuggestionOperation,
|
CompilerSuggestionOperation,
|
||||||
ErrorSeverity,
|
|
||||||
parsePluginOptions,
|
|
||||||
validateEnvironmentConfig,
|
|
||||||
OPT_OUT_DIRECTIVES,
|
|
||||||
type PluginOptions,
|
|
||||||
} from 'babel-plugin-react-compiler/src';
|
} from 'babel-plugin-react-compiler/src';
|
||||||
import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
|
|
||||||
import type {Rule} from 'eslint';
|
import type {Rule} from 'eslint';
|
||||||
import {Statement} from 'estree';
|
import runReactCompiler, {RunCacheEntry} from '../shared/RunReactCompiler';
|
||||||
import * as HermesParser from 'hermes-parser';
|
import {
|
||||||
|
LintRules,
|
||||||
|
type LintRule,
|
||||||
|
} from 'babel-plugin-react-compiler/src/CompilerError';
|
||||||
|
|
||||||
function assertExhaustive(_: never, errorMsg: string): never {
|
function assertExhaustive(_: never, errorMsg: string): never {
|
||||||
throw new Error(errorMsg);
|
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(
|
function makeSuggestions(
|
||||||
detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions,
|
detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions,
|
||||||
): Array<Rule.SuggestionReportDescriptor> {
|
): Array<Rule.SuggestionReportDescriptor> {
|
||||||
|
|
@ -95,166 +75,30 @@ function makeSuggestions(
|
||||||
return suggest;
|
return suggest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMPILER_OPTIONS: Partial<PluginOptions> = {
|
function getReactCompilerResult(context: Rule.RuleContext): RunCacheEntry {
|
||||||
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
|
// Compat with older versions of eslint
|
||||||
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
||||||
const filename = context.filename ?? context.getFilename();
|
const filename = context.filename ?? context.getFilename();
|
||||||
const userOpts = context.options[0] ?? {};
|
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 results = runReactCompiler({
|
||||||
const options: PluginOptions = parsePluginOptions({
|
sourceCode,
|
||||||
...COMPILER_OPTIONS,
|
filename,
|
||||||
...userOpts,
|
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();
|
return results;
|
||||||
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(
|
function hasFlowSuppression(
|
||||||
|
program: RunCacheEntry,
|
||||||
nodeLoc: BabelSourceLocation,
|
nodeLoc: BabelSourceLocation,
|
||||||
suppression: string,
|
suppressions: Array<string>,
|
||||||
): boolean {
|
): boolean {
|
||||||
const comments = sourceCode.getAllComments();
|
for (const commentNode of program.flowSuppressions) {
|
||||||
const flowSuppressionRegex = new RegExp(
|
|
||||||
'\\$FlowFixMe\\[' + suppression + '\\]',
|
|
||||||
);
|
|
||||||
for (const commentNode of comments) {
|
|
||||||
if (
|
if (
|
||||||
flowSuppressionRegex.test(commentNode.value) &&
|
suppressions.includes(commentNode.code) &&
|
||||||
commentNode.loc!.end.line === nodeLoc.start.line - 1
|
commentNode.line === nodeLoc.start.line - 1
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -262,96 +106,112 @@ const rule: Rule.RuleModule = {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let babelAST;
|
function makeRule(rule: LintRule): Rule.RuleModule {
|
||||||
if (filename.endsWith('.tsx') || filename.endsWith('.ts')) {
|
const create = (context: Rule.RuleContext): Rule.RuleListener => {
|
||||||
try {
|
const result = getReactCompilerResult(context);
|
||||||
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) {
|
for (const event of result.events) {
|
||||||
try {
|
if (event.kind === 'CompileError') {
|
||||||
transformFromAstSync(babelAST, sourceCode.text, {
|
const detail = event.detail;
|
||||||
filename,
|
if (detail.category === rule.category) {
|
||||||
highlightCode: false,
|
const loc = detail.primaryLocation();
|
||||||
retainLines: true,
|
if (loc == null || typeof loc === 'symbol') {
|
||||||
plugins: [
|
continue;
|
||||||
[PluginProposalPrivateMethods, {loose: true}],
|
|
||||||
[BabelPluginReactCompiler, options],
|
|
||||||
],
|
|
||||||
sourceType: 'module',
|
|
||||||
configFile: false,
|
|
||||||
babelrc: false,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
/* errors handled by injected logger */
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function reportUnusedOptOutDirective(stmt: Statement) {
|
|
||||||
if (
|
if (
|
||||||
stmt.type === 'ExpressionStatement' &&
|
hasFlowSuppression(result, loc, [
|
||||||
stmt.expression.type === 'Literal' &&
|
'react-rule-hook',
|
||||||
typeof stmt.expression.value === 'string' &&
|
'react-rule-unsafe-ref',
|
||||||
OPT_OUT_DIRECTIVES.has(stmt.expression.value) &&
|
])
|
||||||
stmt.loc != null
|
|
||||||
) {
|
) {
|
||||||
|
// 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({
|
context.report({
|
||||||
message: `Unused '${stmt.expression.value}' directive`,
|
message: detail.printErrorMessage(result.sourceCode, {
|
||||||
loc: stmt.loc,
|
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: [
|
suggest: [
|
||||||
{
|
{
|
||||||
desc: 'Remove the directive',
|
desc: 'Remove the directive',
|
||||||
fix(fixer) {
|
fix(fixer): Rule.Fix {
|
||||||
return fixer.remove(stmt);
|
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,
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -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<PluginOptions> = {
|
||||||
|
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<UnusedOptOutDirective>;
|
||||||
|
events: Array<LoggerEvent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RunParams = {
|
||||||
|
sourceCode: SourceCode;
|
||||||
|
filename: string;
|
||||||
|
userOpts: PluginOptions;
|
||||||
|
};
|
||||||
|
const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g;
|
||||||
|
|
||||||
|
function getFlowSuppressions(
|
||||||
|
sourceCode: SourceCode,
|
||||||
|
): Array<{line: number; code: string}> {
|
||||||
|
const comments = sourceCode.getAllComments();
|
||||||
|
const results: Array<{line: number; code: string}> = [];
|
||||||
|
|
||||||
|
for (const commentNode of comments) {
|
||||||
|
const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX);
|
||||||
|
for (const match of matches) {
|
||||||
|
if (match.index != null && commentNode.loc != null) {
|
||||||
|
const code = match[1];
|
||||||
|
results.push({
|
||||||
|
line: commentNode.loc!.end.line,
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterUnusedOptOutDirectives(
|
||||||
|
directives: ReadonlyArray<Directive>,
|
||||||
|
): Array<UnusedOptOutDirective> {
|
||||||
|
const results: Array<UnusedOptOutDirective> = [];
|
||||||
|
for (const directive of directives) {
|
||||||
|
if (
|
||||||
|
OPT_OUT_DIRECTIVES.has(directive.value.value) &&
|
||||||
|
directive.loc != null
|
||||||
|
) {
|
||||||
|
results.push({
|
||||||
|
loc: directive.loc,
|
||||||
|
directive: directive.value.value,
|
||||||
|
range: [directive.start!, directive.end!],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runReactCompilerImpl({
|
||||||
|
sourceCode,
|
||||||
|
filename,
|
||||||
|
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<File> | 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<K, T> {
|
||||||
|
// 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<string, RunCacheEntry>(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};
|
||||||
|
}
|
||||||
|
|
@ -338,7 +338,16 @@ export async function transformFixtureInput(
|
||||||
if (logs.length !== 0) {
|
if (logs.length !== 0) {
|
||||||
formattedLogs = logs
|
formattedLogs = logs
|
||||||
.map(({event}) => {
|
.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');
|
.join('\n');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,9 @@
|
||||||
import type {Linter} from 'eslint';
|
import {defineConfig} from 'eslint/config';
|
||||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
|
||||||
export default [
|
console.log(reactHooks.configs['recommended-latest']);
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
|
|
@ -12,11 +14,12 @@ export default [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
},
|
},
|
||||||
reactHooks.configs['recommended'],
|
extends: ['react-hooks/recommended-latest'],
|
||||||
{
|
|
||||||
rules: {
|
rules: {
|
||||||
'react-hooks/exhaustive-deps': 'error',
|
'react-hooks/exhaustive-deps': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] satisfies Linter.Config[];
|
]);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "eslint-v9",
|
"name": "eslint-v9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.33.0",
|
||||||
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
|
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
|
||||||
"jiti": "^2.4.2"
|
"jiti": "^2.4.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -221,26 +221,31 @@
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
|
||||||
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
|
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
|
||||||
|
|
||||||
"@eslint/config-array@^0.19.2":
|
"@eslint/config-array@^0.21.0":
|
||||||
version "0.19.2"
|
version "0.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa"
|
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636"
|
||||||
integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==
|
integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/object-schema" "^2.1.6"
|
"@eslint/object-schema" "^2.1.6"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
|
|
||||||
"@eslint/core@^0.12.0":
|
"@eslint/config-helpers@^0.3.1":
|
||||||
version "0.12.0"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e"
|
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617"
|
||||||
integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==
|
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:
|
dependencies:
|
||||||
"@types/json-schema" "^7.0.15"
|
"@types/json-schema" "^7.0.15"
|
||||||
|
|
||||||
"@eslint/eslintrc@^3.3.0":
|
"@eslint/eslintrc@^3.3.1":
|
||||||
version "3.3.0"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846"
|
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964"
|
||||||
integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==
|
integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^6.12.4"
|
ajv "^6.12.4"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
|
|
@ -252,22 +257,22 @@
|
||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
"@eslint/js@9.21.0":
|
"@eslint/js@9.33.0":
|
||||||
version "9.21.0"
|
version "9.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.21.0.tgz#4303ef4e07226d87c395b8fad5278763e9c15c08"
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.33.0.tgz#475c92fdddab59b8b8cab960e3de2564a44bf368"
|
||||||
integrity sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==
|
integrity sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==
|
||||||
|
|
||||||
"@eslint/object-schema@^2.1.6":
|
"@eslint/object-schema@^2.1.6":
|
||||||
version "2.1.6"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
|
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
|
||||||
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
|
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
|
||||||
|
|
||||||
"@eslint/plugin-kit@^0.2.7":
|
"@eslint/plugin-kit@^0.3.5":
|
||||||
version "0.2.7"
|
version "0.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27"
|
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5"
|
||||||
integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==
|
integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/core" "^0.12.0"
|
"@eslint/core" "^0.15.2"
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
|
|
||||||
"@humanfs/core@^0.19.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"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
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:
|
ajv@^6.12.4:
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
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"
|
version "0.0.0"
|
||||||
uid ""
|
uid ""
|
||||||
|
|
||||||
eslint-scope@^8.2.0:
|
eslint-scope@^8.4.0:
|
||||||
version "8.2.0"
|
version "8.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"
|
||||||
integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==
|
integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==
|
||||||
dependencies:
|
dependencies:
|
||||||
esrecurse "^4.3.0"
|
esrecurse "^4.3.0"
|
||||||
estraverse "^5.2.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"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
|
||||||
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
|
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
|
||||||
|
|
||||||
eslint@^9.18.0:
|
eslint-visitor-keys@^4.2.1:
|
||||||
version "9.21.0"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.21.0.tgz#b1c9c16f5153ff219791f627b94ab8f11f811591"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
|
||||||
integrity sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==
|
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:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.2.0"
|
"@eslint-community/eslint-utils" "^4.2.0"
|
||||||
"@eslint-community/regexpp" "^4.12.1"
|
"@eslint-community/regexpp" "^4.12.1"
|
||||||
"@eslint/config-array" "^0.19.2"
|
"@eslint/config-array" "^0.21.0"
|
||||||
"@eslint/core" "^0.12.0"
|
"@eslint/config-helpers" "^0.3.1"
|
||||||
"@eslint/eslintrc" "^3.3.0"
|
"@eslint/core" "^0.15.2"
|
||||||
"@eslint/js" "9.21.0"
|
"@eslint/eslintrc" "^3.3.1"
|
||||||
"@eslint/plugin-kit" "^0.2.7"
|
"@eslint/js" "9.33.0"
|
||||||
|
"@eslint/plugin-kit" "^0.3.5"
|
||||||
"@humanfs/node" "^0.16.6"
|
"@humanfs/node" "^0.16.6"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
"@humanwhocodes/retry" "^0.4.2"
|
"@humanwhocodes/retry" "^0.4.2"
|
||||||
|
|
@ -515,9 +531,9 @@ eslint@^9.18.0:
|
||||||
cross-spawn "^7.0.6"
|
cross-spawn "^7.0.6"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
escape-string-regexp "^4.0.0"
|
escape-string-regexp "^4.0.0"
|
||||||
eslint-scope "^8.2.0"
|
eslint-scope "^8.4.0"
|
||||||
eslint-visitor-keys "^4.2.0"
|
eslint-visitor-keys "^4.2.1"
|
||||||
espree "^10.3.0"
|
espree "^10.4.0"
|
||||||
esquery "^1.5.0"
|
esquery "^1.5.0"
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
fast-deep-equal "^3.1.3"
|
fast-deep-equal "^3.1.3"
|
||||||
|
|
@ -533,7 +549,7 @@ eslint@^9.18.0:
|
||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
optionator "^0.9.3"
|
optionator "^0.9.3"
|
||||||
|
|
||||||
espree@^10.0.1, espree@^10.3.0:
|
espree@^10.0.1:
|
||||||
version "10.3.0"
|
version "10.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a"
|
||||||
integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==
|
integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==
|
||||||
|
|
@ -542,6 +558,15 @@ espree@^10.0.1, espree@^10.3.0:
|
||||||
acorn-jsx "^5.3.2"
|
acorn-jsx "^5.3.2"
|
||||||
eslint-visitor-keys "^4.2.0"
|
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:
|
esquery@^1.5.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
|
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
||||||
"@babel/plugin-transform-object-super": "^7.10.4",
|
"@babel/plugin-transform-object-super": "^7.10.4",
|
||||||
"@babel/plugin-transform-parameters": "^7.10.5",
|
"@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": "^7.23.4",
|
||||||
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
|
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
|
||||||
"@babel/plugin-transform-react-jsx-source": "^7.10.5",
|
"@babel/plugin-transform-react-jsx-source": "^7.10.5",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
const ESLintTesterV7 = require('eslint-v7').RuleTester;
|
const ESLintTesterV7 = require('eslint-v7').RuleTester;
|
||||||
const ESLintTesterV9 = require('eslint-v9').RuleTester;
|
const ESLintTesterV9 = require('eslint-v9').RuleTester;
|
||||||
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
|
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
|
* A string template tag that removes padding from the left side of multi-line strings
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
const ESLintTesterV7 = require('eslint-v7').RuleTester;
|
const ESLintTesterV7 = require('eslint-v7').RuleTester;
|
||||||
const ESLintTesterV9 = require('eslint-v9').RuleTester;
|
const ESLintTesterV9 = require('eslint-v9').RuleTester;
|
||||||
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
|
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
|
* A string template tag that removes padding from the left side of multi-line strings
|
||||||
|
|
|
||||||
|
|
@ -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 <div onClick={onClick}>{text}</div>;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}`,
|
|
||||||
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 <div>{props.foo}</div>;
|
|
||||||
}`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: /React Compiler has skipped optimizing this component/,
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output: normalizeIndent`
|
|
||||||
function Component(props) {
|
|
||||||
|
|
||||||
return <div>{props.foo}</div>;
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}
|
|
||||||
function Bar(props) {
|
|
||||||
props.a.b = 2;
|
|
||||||
return <div>{props.c}</div>
|
|
||||||
}`,
|
|
||||||
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 <div>{y * x}</div>;
|
|
||||||
}`,
|
|
||||||
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 <div>Hello world {count}</div>
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
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 <div>Hello world</div>
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: "Unused 'use no forget' directive",
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output:
|
|
||||||
// yuck
|
|
||||||
'\nfunction Component() {\n \n return <div>Hello world</div>\n}\n',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Unused 'use no forget' directive is reported when no errors are present on non-components or hooks",
|
|
||||||
code: normalizeIndent`
|
|
||||||
function notacomponent() {
|
|
||||||
'use no forget';
|
|
||||||
return 1 + 1;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: "Unused 'use no forget' directive",
|
|
||||||
suggestions: [
|
|
||||||
{
|
|
||||||
output:
|
|
||||||
// yuck
|
|
||||||
'\nfunction notacomponent() {\n \n return 1 + 1;\n}\n',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 <div>Hello world</div>;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
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);
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RuleTester} from 'eslint';
|
import {RuleTester} from 'eslint';
|
||||||
import ReactCompilerRule from '../src/rules/ReactCompiler';
|
import {allRules} from '../src/shared/ReactCompiler';
|
||||||
|
|
||||||
const ESLintTesterV8 = require('eslint-v8').RuleTester;
|
const ESLintTesterV8 = require('eslint-v8').RuleTester;
|
||||||
|
|
||||||
|
|
@ -74,4 +74,4 @@ const tests: CompilerTestCases = {
|
||||||
const eslintTester = new ESLintTesterV8({
|
const eslintTester = new ESLintTesterV8({
|
||||||
parser: require.resolve('@typescript-eslint/parser-v5'),
|
parser: require.resolve('@typescript-eslint/parser-v5'),
|
||||||
});
|
});
|
||||||
eslintTester.run('react-compiler', ReactCompilerRule, tests);
|
eslintTester.run('react-compiler', allRules['immutability'], tests);
|
||||||
|
|
|
||||||
|
|
@ -4,63 +4,64 @@
|
||||||
* This source code is licensed under the MIT license found in the
|
* This source code is licensed under the MIT license found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* 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 ExhaustiveDeps from './rules/ExhaustiveDeps';
|
||||||
import ReactCompiler from './rules/ReactCompiler';
|
import {allRules, recommendedRules} from './shared/ReactCompiler';
|
||||||
import RulesOfHooks from './rules/RulesOfHooks';
|
import RulesOfHooks from './rules/RulesOfHooks';
|
||||||
|
|
||||||
// All rules
|
// All rules
|
||||||
const rules = {
|
const rules = {
|
||||||
'exhaustive-deps': ExhaustiveDeps,
|
'exhaustive-deps': ExhaustiveDeps,
|
||||||
'react-compiler': ReactCompiler,
|
|
||||||
'rules-of-hooks': RulesOfHooks,
|
'rules-of-hooks': RulesOfHooks,
|
||||||
|
...allRules,
|
||||||
} satisfies Record<string, Rule.RuleModule>;
|
} satisfies Record<string, Rule.RuleModule>;
|
||||||
|
|
||||||
// Config rules
|
// Config rules
|
||||||
const configRules = {
|
const ruleConfigs = {
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
'react-hooks/exhaustive-deps': 'warn',
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.keys(recommendedRules).map(name => ['react-hooks/' + name, 'error']),
|
||||||
|
),
|
||||||
} satisfies Linter.RulesRecord;
|
} satisfies Linter.RulesRecord;
|
||||||
|
|
||||||
// Flat config
|
const plugin = {
|
||||||
const recommendedConfig = {
|
meta: {
|
||||||
name: 'react-hooks/recommended',
|
name: 'eslint-plugin-react-hooks',
|
||||||
plugins: {
|
|
||||||
get 'react-hooks'(): ESLint.Plugin {
|
|
||||||
return plugin;
|
|
||||||
},
|
},
|
||||||
},
|
configs: {},
|
||||||
rules: configRules,
|
rules,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plugin object
|
Object.assign(plugin.configs, {
|
||||||
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': {
|
'recommended-legacy': {
|
||||||
plugins: ['react-hooks'],
|
plugins: ['react-hooks'],
|
||||||
rules: configRules,
|
rules: ruleConfigs,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
'flat/recommended': [
|
||||||
* Recommended config, to be used with flat configs.
|
{
|
||||||
*/
|
plugins: {
|
||||||
recommended: recommendedConfig,
|
'react-hooks': plugin,
|
||||||
|
|
||||||
/** @deprecated please use `recommended`; will be removed in v7 */
|
|
||||||
'recommended-latest': recommendedConfig,
|
|
||||||
},
|
},
|
||||||
} satisfies ESLint.Plugin;
|
rules: ruleConfigs,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
const configs = plugin.configs;
|
'recommended-latest': [
|
||||||
const meta = plugin.meta;
|
{
|
||||||
export {configs, meta, rules};
|
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
|
recommended: {
|
||||||
// instead of individual named exports.
|
plugins: ['react-hooks'],
|
||||||
// export default plugin;
|
rules: ruleConfigs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
|
|
|
||||||
|
|
@ -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<Rule.SuggestionReportDescriptor> {
|
|
||||||
const suggest: Array<Rule.SuggestionReportDescriptor> = [];
|
|
||||||
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<PluginOptions> = {
|
|
||||||
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;
|
|
||||||
216
packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
Normal file
216
packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
Normal file
|
|
@ -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<Rule.SuggestionReportDescriptor> {
|
||||||
|
const suggest: Array<Rule.SuggestionReportDescriptor> = [];
|
||||||
|
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<string>,
|
||||||
|
): 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,
|
||||||
|
);
|
||||||
|
|
@ -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<PluginOptions> = {
|
||||||
|
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<UnusedOptOutDirective>;
|
||||||
|
events: Array<LoggerEvent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RunParams = {
|
||||||
|
sourceCode: SourceCode;
|
||||||
|
filename: string;
|
||||||
|
userOpts: PluginOptions;
|
||||||
|
};
|
||||||
|
const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g;
|
||||||
|
|
||||||
|
function getFlowSuppressions(
|
||||||
|
sourceCode: SourceCode,
|
||||||
|
): Array<{line: number; code: string}> {
|
||||||
|
const comments = sourceCode.getAllComments();
|
||||||
|
const results: Array<{line: number; code: string}> = [];
|
||||||
|
|
||||||
|
for (const commentNode of comments) {
|
||||||
|
const matches = commentNode.value.matchAll(FLOW_SUPPRESSION_REGEX);
|
||||||
|
for (const match of matches) {
|
||||||
|
if (match.index != null && commentNode.loc != null) {
|
||||||
|
const code = match[1];
|
||||||
|
results.push({
|
||||||
|
line: commentNode.loc!.end.line,
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterUnusedOptOutDirectives(
|
||||||
|
directives: ReadonlyArray<Directive>,
|
||||||
|
): Array<UnusedOptOutDirective> {
|
||||||
|
const results: Array<UnusedOptOutDirective> = [];
|
||||||
|
for (const directive of directives) {
|
||||||
|
if (
|
||||||
|
OPT_OUT_DIRECTIVES.has(directive.value.value) &&
|
||||||
|
directive.loc != null
|
||||||
|
) {
|
||||||
|
results.push({
|
||||||
|
loc: directive.loc,
|
||||||
|
directive: directive.value.value,
|
||||||
|
range: [directive.start!, directive.end!],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runReactCompilerImpl({
|
||||||
|
sourceCode,
|
||||||
|
filename,
|
||||||
|
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<File> | 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<K, T> {
|
||||||
|
// 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<string, RunCacheEntry>(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};
|
||||||
|
}
|
||||||
|
|
@ -403,6 +403,7 @@ function getPlugins(
|
||||||
// Use Node resolution mechanism.
|
// Use Node resolution mechanism.
|
||||||
resolve({
|
resolve({
|
||||||
// skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0
|
// skip: externals, // TODO: options.skip was removed in @rollup/plugin-node-resolve 3.0.0
|
||||||
|
preferBuiltins: bundle.preferBuiltins,
|
||||||
}),
|
}),
|
||||||
// Remove license headers from individual modules
|
// Remove license headers from individual modules
|
||||||
stripBanner({
|
stripBanner({
|
||||||
|
|
|
||||||
|
|
@ -1208,7 +1208,14 @@ const bundles = [
|
||||||
global: 'ESLintPluginReactHooks',
|
global: 'ESLintPluginReactHooks',
|
||||||
minifyWithProdErrorCodes: false,
|
minifyWithProdErrorCodes: false,
|
||||||
wrapWithModuleBoundaries: 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',
|
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`,
|
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 = {
|
module.exports = {
|
||||||
bundleTypes,
|
bundleTypes,
|
||||||
moduleTypes,
|
moduleTypes,
|
||||||
bundles,
|
bundles: activeBundles,
|
||||||
getFilename,
|
getFilename,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
232
yarn.lock
232
yarn.lock
|
|
@ -70,6 +70,15 @@
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
picocolors "^1.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":
|
"@babel/code-frame@^7.8.3":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
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"
|
"@jridgewell/trace-mapping" "^0.3.25"
|
||||||
jsesc "^3.0.2"
|
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":
|
"@babel/generator@^7.8.3":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
|
||||||
|
|
@ -235,6 +255,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.25.9"
|
"@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":
|
"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
|
||||||
version "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"
|
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"
|
"@babel/traverse" "^7.26.9"
|
||||||
semver "^6.3.1"
|
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":
|
"@babel/helper-create-regexp-features-plugin@^7.10.4":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
"@babel/types" "^7.8.3"
|
"@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":
|
"@babel/helper-hoist-variables@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
|
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/traverse" "^7.25.9"
|
||||||
"@babel/types" "^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":
|
"@babel/helper-module-imports@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
|
||||||
|
|
@ -561,6 +614,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.25.9"
|
"@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":
|
"@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"
|
version "7.24.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a"
|
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"
|
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==
|
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":
|
"@babel/helper-plugin-utils@^7.8.3":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
|
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/helper-optimise-call-expression" "^7.25.9"
|
||||||
"@babel/traverse" "^7.26.5"
|
"@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":
|
"@babel/helper-simple-access@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
|
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/traverse" "^7.25.9"
|
||||||
"@babel/types" "^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":
|
"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0":
|
||||||
version "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"
|
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"
|
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==
|
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":
|
"@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"
|
version "7.24.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62"
|
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"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
|
||||||
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
|
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":
|
"@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"
|
version "7.23.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
|
||||||
|
|
@ -849,6 +941,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.26.10"
|
"@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":
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.25.9"
|
"@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":
|
"@babel/plugin-transform-private-methods@^7.24.4", "@babel/plugin-transform-private-methods@^7.25.9":
|
||||||
version "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"
|
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/parser" "^7.26.9"
|
||||||
"@babel/types" "^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":
|
"@babel/template@^7.8.3":
|
||||||
version "7.8.3"
|
version "7.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
|
||||||
|
|
@ -2446,6 +2562,19 @@
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
globals "^11.1.0"
|
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":
|
"@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"
|
version "7.26.9"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
|
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-string-parser" "^7.25.9"
|
||||||
"@babel/helper-validator-identifier" "^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":
|
"@bcoe/v8-coverage@^0.2.3":
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
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/sourcemap-codec" "^1.4.10"
|
||||||
"@jridgewell/trace-mapping" "^0.3.9"
|
"@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":
|
"@jridgewell/gen-mapping@^0.3.2":
|
||||||
version "0.3.8"
|
version "0.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142"
|
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/resolve-uri" "3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
"@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":
|
"@leichtgewicht/ip-codec@^2.0.1":
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
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:
|
dependencies:
|
||||||
eslint-visitor-keys "^1.1.0"
|
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"
|
version "7.32.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
|
||||||
integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
|
integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
|
||||||
|
|
@ -8299,52 +8452,6 @@ eslint@8.57.0:
|
||||||
strip-ansi "^6.0.1"
|
strip-ansi "^6.0.1"
|
||||||
text-table "^0.2.0"
|
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:
|
espree@10.0.1, espree@^10.0.1:
|
||||||
version "10.0.1"
|
version "10.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
|
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"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
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"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
|
@ -15995,15 +16102,6 @@ string-width@^4.0.0:
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
strip-ansi "^6.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:
|
string-width@^5.0.1, string-width@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
|
|
@ -16064,7 +16162,7 @@ string_decoder@~1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
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"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
|
@ -16092,13 +16190,6 @@ strip-ansi@^5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
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:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
|
|
@ -17681,7 +17772,7 @@ workerize-loader@^2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
loader-utils "^2.0.0"
|
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"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
|
@ -17699,15 +17790,6 @@ wrap-ansi@^6.2.0:
|
||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.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:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user