1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import * as t from '@babel/types';9import {codeFrameColumns} from '@babel/code-frame';10import {type SourceLocation} from './HIR';11import {Err, Ok, Result} from './Utils/Result';12import {assertExhaustive} from './Utils/utils';13import invariant from 'invariant';1415// Number of context lines to display above the source of an error16const CODEFRAME_LINES_ABOVE = 2;17// Number of context lines to display below the source of an error18const CODEFRAME_LINES_BELOW = 3;19/*20 * Max number of lines for the _source_ of an error, before we abbreviate21 * the display of the source portion22 */23const CODEFRAME_MAX_LINES = 10;24/*25 * When the error source exceeds the above threshold, how many lines of26 * the source should be displayed? We show:27 * - CODEFRAME_LINES_ABOVE context lines28 * - CODEFRAME_ABBREVIATED_SOURCE_LINES of the error29 * - '...' ellipsis30 * - CODEFRAME_ABBREVIATED_SOURCE_LINES of the error31 * - CODEFRAME_LINES_BELOW context lines32 *33 * This value must be at least 2 or else we'll cut off important parts of the error message34 */35const CODEFRAME_ABBREVIATED_SOURCE_LINES = 5;3637export enum ErrorSeverity {38 /**39 * An actionable error that the developer can fix. For example, product code errors should be40 * reported as such.41 */42 Error = 'Error',43 /**44 * An error that the developer may not necessarily be able to fix. For example, syntax not45 * supported by the compiler does not indicate any fault in the product code.46 */47 Warning = 'Warning',48 /**49 * Not an error. These will not be surfaced in ESLint, but may be surfaced in other ways50 * (eg Forgive) where informational hints can be shown.51 */52 Hint = 'Hint',53 /**54 * These errors will not be reported anywhere. Useful for work in progress validations.55 */56 Off = 'Off',57}5859export type CompilerDiagnosticOptions = {60 category: ErrorCategory;61 reason: string;62 description: string | null;63 details: Array<CompilerDiagnosticDetail>;64 suggestions?: Array<CompilerSuggestion> | null | undefined;65};6667export type CompilerDiagnosticDetail =68 /**69 * A/the source of the error70 */71 | {72 kind: 'error';73 loc: SourceLocation | null;74 message: string | null;75 }76 | {77 kind: 'hint';78 message: string;79 };8081export enum CompilerSuggestionOperation {82 InsertBefore,83 InsertAfter,84 Remove,85 Replace,86}87export type CompilerSuggestion =88 | {89 op:90 | CompilerSuggestionOperation.InsertAfter91 | CompilerSuggestionOperation.InsertBefore92 | CompilerSuggestionOperation.Replace;93 range: [number, number];94 description: string;95 text: string;96 }97 | {98 op: CompilerSuggestionOperation.Remove;99 range: [number, number];100 description: string;101 };102103/**104 * @deprecated use {@link CompilerDiagnosticOptions} instead105 */106export type CompilerErrorDetailOptions = {107 category: ErrorCategory;108 reason: string;109 description?: string | null | undefined;110 loc: SourceLocation | null;111 suggestions?: Array<CompilerSuggestion> | null | undefined;112};113114export type PrintErrorMessageOptions = {115 /**116 * ESLint uses 1-indexed columns and prints one error at a time117 * So it doesn't require the "Found # error(s)" text118 */119 eslint: boolean;120};121122export class CompilerDiagnostic {123 options: CompilerDiagnosticOptions;124125 constructor(options: CompilerDiagnosticOptions) {126 this.options = options;127 }128129 static create(130 options: Omit<CompilerDiagnosticOptions, 'details'>,131 ): CompilerDiagnostic {132 return new CompilerDiagnostic({...options, details: []});133 }134135 get reason(): CompilerDiagnosticOptions['reason'] {136 return this.options.reason;137 }138 get description(): CompilerDiagnosticOptions['description'] {139 return this.options.description;140 }141 get severity(): ErrorSeverity {142 return getRuleForCategory(this.category).severity;143 }144 get suggestions(): CompilerDiagnosticOptions['suggestions'] {145 return this.options.suggestions;146 }147 get category(): ErrorCategory {148 return this.options.category;149 }150151 withDetails(...details: Array<CompilerDiagnosticDetail>): CompilerDiagnostic {152 this.options.details.push(...details);153 return this;154 }155156 primaryLocation(): SourceLocation | null {157 const firstErrorDetail = this.options.details.filter(158 d => d.kind === 'error',159 )[0];160 return firstErrorDetail != null && firstErrorDetail.kind === 'error'161 ? firstErrorDetail.loc162 : null;163 }164165 printErrorMessage(source: string, options: PrintErrorMessageOptions): string {166 const buffer = [printErrorSummary(this.category, this.reason)];167 if (this.description != null) {168 buffer.push('\n\n', `${this.description}.`);169 }170 for (const detail of this.options.details) {171 switch (detail.kind) {172 case 'error': {173 const loc = detail.loc;174 if (loc == null || typeof loc === 'symbol') {175 continue;176 }177 let codeFrame: string;178 try {179 codeFrame = printCodeFrame(source, loc, detail.message ?? '');180 } catch (e) {181 codeFrame = detail.message ?? '';182 }183 buffer.push('\n\n');184 if (loc.filename != null) {185 const line = loc.start.line;186 const column = options.eslint187 ? loc.start.column + 1188 : loc.start.column;189 buffer.push(`${loc.filename}:${line}:${column}\n`);190 }191 buffer.push(codeFrame);192 break;193 }194 case 'hint': {195 buffer.push('\n\n');196 buffer.push(detail.message);197 break;198 }199 default: {200 assertExhaustive(201 detail,202 `Unexpected detail kind ${(detail as any).kind}`,203 );204 }205 }206 }207 return buffer.join('');208 }209210 toString(): string {211 const buffer = [printErrorSummary(this.category, this.reason)];212 if (this.description != null) {213 buffer.push(`. ${this.description}.`);214 }215 const loc = this.primaryLocation();216 if (loc != null && typeof loc !== 'symbol') {217 buffer.push(` (${loc.start.line}:${loc.start.column})`);218 }219 return buffer.join('');220 }221}222223/**224 * Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then225 * aggregated into a single {@link CompilerError} later.226 *227 * @deprecated use {@link CompilerDiagnostic} instead228 */229export class CompilerErrorDetail {230 options: CompilerErrorDetailOptions;231232 constructor(options: CompilerErrorDetailOptions) {233 this.options = options;234 }235236 get reason(): CompilerErrorDetailOptions['reason'] {237 return this.options.reason;238 }239 get description(): CompilerErrorDetailOptions['description'] {240 return this.options.description;241 }242 get severity(): ErrorSeverity {243 return getRuleForCategory(this.category).severity;244 }245 get loc(): CompilerErrorDetailOptions['loc'] {246 return this.options.loc;247 }248 get suggestions(): CompilerErrorDetailOptions['suggestions'] {249 return this.options.suggestions;250 }251 get category(): ErrorCategory {252 return this.options.category;253 }254255 primaryLocation(): SourceLocation | null {256 return this.loc;257 }258259 printErrorMessage(source: string, options: PrintErrorMessageOptions): string {260 const buffer = [printErrorSummary(this.category, this.reason)];261 if (this.description != null) {262 buffer.push(`\n\n${this.description}.`);263 }264 const loc = this.loc;265 if (loc != null && typeof loc !== 'symbol') {266 let codeFrame: string;267 try {268 codeFrame = printCodeFrame(source, loc, this.reason);269 } catch (e) {270 codeFrame = '';271 }272 buffer.push(`\n\n`);273 if (loc.filename != null) {274 const line = loc.start.line;275 const column = options.eslint ? loc.start.column + 1 : loc.start.column;276 buffer.push(`${loc.filename}:${line}:${column}\n`);277 }278 buffer.push(codeFrame);279 buffer.push('\n\n');280 }281 return buffer.join('');282 }283284 toString(): string {285 const buffer = [printErrorSummary(this.category, this.reason)];286 if (this.description != null) {287 buffer.push(`. ${this.description}.`);288 }289 const loc = this.loc;290 if (loc != null && typeof loc !== 'symbol') {291 buffer.push(` (${loc.start.line}:${loc.start.column})`);292 }293 return buffer.join('');294 }295}296297/**298 * An aggregate of {@link CompilerDiagnostic}. This allows us to aggregate all issues found by the299 * compiler into a single error before we throw. Where possible, prefer to push diagnostics into300 * the error aggregate instead of throwing immediately.301 */302export class CompilerError extends Error {303 details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];304 disabledDetails: Array<CompilerErrorDetail | CompilerDiagnostic> = [];305 printedMessage: string | null = null;306307 static invariant(308 condition: unknown,309 options: {310 reason: CompilerDiagnosticOptions['reason'];311 description?: CompilerDiagnosticOptions['description'];312 message?: string | null;313 loc: SourceLocation;314 },315 ): asserts condition {316 if (!condition) {317 const errors = new CompilerError();318 errors.pushDiagnostic(319 CompilerDiagnostic.create({320 reason: options.reason,321 description: options.description ?? null,322 category: ErrorCategory.Invariant,323 }).withDetails({324 kind: 'error',325 loc: options.loc,326 message: options.message ?? options.reason,327 }),328 );329 throw errors;330 }331 }332333 static throwDiagnostic(options: CompilerDiagnosticOptions): never {334 const errors = new CompilerError();335 errors.pushDiagnostic(new CompilerDiagnostic(options));336 throw errors;337 }338339 static throwTodo(340 options: Omit<CompilerErrorDetailOptions, 'category'>,341 ): never {342 const errors = new CompilerError();343 errors.pushErrorDetail(344 new CompilerErrorDetail({345 ...options,346 category: ErrorCategory.Todo,347 }),348 );349 throw errors;350 }351352 static throwInvalidJS(353 options: Omit<CompilerErrorDetailOptions, 'category'>,354 ): never {355 const errors = new CompilerError();356 errors.pushErrorDetail(357 new CompilerErrorDetail({358 ...options,359 category: ErrorCategory.Syntax,360 }),361 );362 throw errors;363 }364365 static throwInvalidReact(options: CompilerErrorDetailOptions): never {366 const errors = new CompilerError();367 errors.pushErrorDetail(new CompilerErrorDetail(options));368 throw errors;369 }370371 static throwInvalidConfig(372 options: Omit<CompilerErrorDetailOptions, 'category'>,373 ): never {374 const errors = new CompilerError();375 errors.pushErrorDetail(376 new CompilerErrorDetail({377 ...options,378 category: ErrorCategory.Config,379 }),380 );381 throw errors;382 }383384 static throw(options: CompilerErrorDetailOptions): never {385 const errors = new CompilerError();386 errors.pushErrorDetail(new CompilerErrorDetail(options));387 throw errors;388 }389390 constructor(...args: Array<any>) {391 super(...args);392 this.name = 'ReactCompilerError';393 this.details = [];394 this.disabledDetails = [];395 }396397 override get message(): string {398 return this.printedMessage ?? this.toString();399 }400401 override set message(_message: string) {}402403 override toString(): string {404 if (this.printedMessage) {405 return this.printedMessage;406 }407 if (Array.isArray(this.details)) {408 return this.details.map(detail => detail.toString()).join('\n\n');409 }410 return this.name;411 }412413 withPrintedMessage(414 source: string,415 options: PrintErrorMessageOptions,416 ): CompilerError {417 this.printedMessage = this.printErrorMessage(source, options);418 return this;419 }420421 printErrorMessage(source: string, options: PrintErrorMessageOptions): string {422 if (options.eslint && this.details.length === 1) {423 return this.details[0].printErrorMessage(source, options);424 }425 return (426 `Found ${this.details.length} error${this.details.length === 1 ? '' : 's'}:\n\n` +427 this.details428 .map(detail => detail.printErrorMessage(source, options).trim())429 .join('\n\n')430 );431 }432433 merge(other: CompilerError): void {434 this.details.push(...other.details);435 this.disabledDetails.push(...other.disabledDetails);436 }437438 pushDiagnostic(diagnostic: CompilerDiagnostic): void {439 if (diagnostic.severity === ErrorSeverity.Off) {440 this.disabledDetails.push(diagnostic);441 } else {442 this.details.push(diagnostic);443 }444 }445446 /**447 * @deprecated use {@link pushDiagnostic} instead448 */449 push(options: CompilerErrorDetailOptions): CompilerErrorDetail {450 const detail = new CompilerErrorDetail({451 category: options.category,452 reason: options.reason,453 description: options.description ?? null,454 suggestions: options.suggestions,455 loc: typeof options.loc === 'symbol' ? null : options.loc,456 });457 return this.pushErrorDetail(detail);458 }459460 /**461 * @deprecated use {@link pushDiagnostic} instead462 */463 pushErrorDetail(detail: CompilerErrorDetail): CompilerErrorDetail {464 if (detail.severity === ErrorSeverity.Off) {465 this.disabledDetails.push(detail);466 } else {467 this.details.push(detail);468 }469 return detail;470 }471472 hasAnyErrors(): boolean {473 return this.details.length > 0;474 }475476 asResult(): Result<void, CompilerError> {477 return this.hasAnyErrors() ? Err(this) : Ok(undefined);478 }479480 /**481 * Returns true if any of the error details are of severity Error.482 */483 hasErrors(): boolean {484 for (const detail of this.details) {485 if (detail.severity === ErrorSeverity.Error) {486 return true;487 }488 }489 return false;490 }491492 /**493 * Returns true if there are no Errors and there is at least one Warning.494 */495 hasWarning(): boolean {496 let res = false;497 for (const detail of this.details) {498 if (detail.severity === ErrorSeverity.Error) {499 return false;500 }501 if (detail.severity === ErrorSeverity.Warning) {502 res = true;503 }504 }505 return res;506 }507508 hasHints(): boolean {509 let res = false;510 for (const detail of this.details) {511 if (detail.severity === ErrorSeverity.Error) {512 return false;513 }514 if (detail.severity === ErrorSeverity.Warning) {515 return false;516 }517 if (detail.severity === ErrorSeverity.Hint) {518 res = true;519 }520 }521 return res;522 }523}524525function printCodeFrame(526 source: string,527 loc: t.SourceLocation,528 message: string,529): string {530 const printed = codeFrameColumns(531 source,532 {533 start: {534 line: loc.start.line,535 column: loc.start.column + 1,536 },537 end: {538 line: loc.end.line,539 column: loc.end.column + 1,540 },541 },542 {543 message,544 linesAbove: CODEFRAME_LINES_ABOVE,545 linesBelow: CODEFRAME_LINES_BELOW,546 },547 );548 const lines = printed.split(/\r?\n/);549 if (loc.end.line - loc.start.line < CODEFRAME_MAX_LINES) {550 return printed;551 }552 const pipeIndex = lines[0].indexOf('|');553 return [554 ...lines.slice(555 0,556 CODEFRAME_LINES_ABOVE + CODEFRAME_ABBREVIATED_SOURCE_LINES,557 ),558 ' '.repeat(pipeIndex) + '…',559 ...lines.slice(560 -(CODEFRAME_LINES_BELOW + CODEFRAME_ABBREVIATED_SOURCE_LINES),561 ),562 ].join('\n');563}564565function printErrorSummary(category: ErrorCategory, message: string): string {566 let heading: string;567 switch (category) {568 case ErrorCategory.CapitalizedCalls:569 case ErrorCategory.Config:570 case ErrorCategory.EffectDerivationsOfState:571 case ErrorCategory.EffectSetState:572 case ErrorCategory.ErrorBoundaries:573 case ErrorCategory.FBT:574 case ErrorCategory.Gating:575 case ErrorCategory.Globals:576 case ErrorCategory.Hooks:577 case ErrorCategory.Immutability:578 case ErrorCategory.Purity:579 case ErrorCategory.Refs:580 case ErrorCategory.RenderSetState:581 case ErrorCategory.StaticComponents:582 case ErrorCategory.Suppression:583 case ErrorCategory.Syntax:584 case ErrorCategory.UseMemo:585 case ErrorCategory.VoidUseMemo:586 case ErrorCategory.MemoDependencies:587 case ErrorCategory.EffectExhaustiveDependencies: {588 heading = 'Error';589 break;590 }591 case ErrorCategory.EffectDependencies:592 case ErrorCategory.IncompatibleLibrary:593 case ErrorCategory.PreserveManualMemo:594 case ErrorCategory.UnsupportedSyntax: {595 heading = 'Compilation Skipped';596 break;597 }598 case ErrorCategory.Invariant: {599 heading = 'Invariant';600 break;601 }602 case ErrorCategory.Todo: {603 heading = 'Todo';604 break;605 }606 default: {607 assertExhaustive(category, `Unhandled category '${category}'`);608 }609 }610 return `${heading}: ${message}`;611}612613/**614 * See getRuleForCategory() for how these map to ESLint rules615 */616export enum ErrorCategory {617 /**618 * Checking for valid hooks usage (non conditional, non-first class, non reactive, etc)619 */620 Hooks = 'Hooks',621 /**622 * Checking for no capitalized calls (not definitively an error, hence separating)623 */624 CapitalizedCalls = 'CapitalizedCalls',625 /**626 * Checking for static components627 */628 StaticComponents = 'StaticComponents',629 /**630 * Checking for valid usage of manual memoization631 */632 UseMemo = 'UseMemo',633 /**634 * Checking that useMemos always return a value635 */636 VoidUseMemo = 'VoidUseMemo',637 /**638 * Checks that manual memoization is preserved639 */640 PreserveManualMemo = 'PreserveManualMemo',641 /**642 * Checks for exhaustive useMemo/useCallback dependencies without extraneous values643 */644 MemoDependencies = 'MemoDependencies',645 /**646 * Checks for known incompatible libraries647 */648 IncompatibleLibrary = 'IncompatibleLibrary',649 /**650 * Checking for no mutations of props, hook arguments, hook return values651 */652 Immutability = 'Immutability',653 /**654 * Checking for assignments to globals655 */656 Globals = 'Globals',657 /**658 * Checking for valid usage of refs, ie no access during render659 */660 Refs = 'Refs',661 /**662 * Checks for memoized effect deps663 */664 EffectDependencies = 'EffectDependencies',665 /**666 * Checks for exhaustive and extraneous effect dependencies667 */668 EffectExhaustiveDependencies = 'EffectExhaustiveDependencies',669 /**670 * Checks for no setState in effect bodies671 */672 EffectSetState = 'EffectSetState',673 EffectDerivationsOfState = 'EffectDerivationsOfState',674 /**675 * Validates against try/catch in place of error boundaries676 */677 ErrorBoundaries = 'ErrorBoundaries',678 /**679 * Checking for pure functions680 */681 Purity = 'Purity',682 /**683 * Validates against setState in render684 */685 RenderSetState = 'RenderSetState',686 /**687 * Internal invariants688 */689 Invariant = 'Invariant',690 /**691 * Todos692 */693 Todo = 'Todo',694 /**695 * Syntax errors696 */697 Syntax = 'Syntax',698 /**699 * Checks for use of unsupported syntax700 */701 UnsupportedSyntax = 'UnsupportedSyntax',702 /**703 * Config errors704 */705 Config = 'Config',706 /**707 * Gating error708 */709 Gating = 'Gating',710 /**711 * Suppressions712 */713 Suppression = 'Suppression',714 /**715 * fbt-specific issues716 */717 FBT = 'FBT',718}719720export enum LintRulePreset {721 /**722 * Rules that are stable and included in the `recommended` preset.723 */724 Recommended = 'recommended',725 /**726 * Rules that are more experimental and only included in the `recommended-latest` preset.727 */728 RecommendedLatest = 'recommended-latest',729 /**730 * Rules that are disabled.731 */732 Off = 'off',733}734735export type LintRule = {736 // Stores the category the rule corresponds to, used to filter errors when reporting737 category: ErrorCategory;738739 // Stores the severity of the error, which is used to map to lint levels such as error/warning.740 severity: ErrorSeverity;741742 /**743 * The "name" of the rule as it will be used by developers to enable/disable, eg744 * "eslint-disable-nest line <name>"745 */746 name: string;747748 /**749 * A description of the rule that appears somewhere in ESLint. This does not affect750 * how error messages are formatted751 */752 description: string;753754 /**755 * Configures the preset in which the rule is enabled. If 'off', the rule will not be included in756 * any preset.757 *758 * NOTE: not all validations are enabled by default! Setting this flag only affects759 * whether a given rule is part of the recommended set. The corresponding validation760 * also should be enabled by default if you want the error to actually show up!761 */762 preset: LintRulePreset;763};764765const RULE_NAME_PATTERN = /^[a-z]+(-[a-z]+)*$/;766767export function getRuleForCategory(category: ErrorCategory): LintRule {768 const rule = getRuleForCategoryImpl(category);769 invariant(770 RULE_NAME_PATTERN.test(rule.name),771 `Invalid rule name, got '${rule.name}' but rules must match ${RULE_NAME_PATTERN.toString()}`,772 );773 return rule;774}775776function getRuleForCategoryImpl(category: ErrorCategory): LintRule {777 switch (category) {778 case ErrorCategory.CapitalizedCalls: {779 return {780 category,781 severity: ErrorSeverity.Error,782 name: 'capitalized-calls',783 description:784 'Validates against calling capitalized functions/methods instead of using JSX',785 preset: LintRulePreset.Off,786 };787 }788 case ErrorCategory.Config: {789 return {790 category,791 severity: ErrorSeverity.Error,792 name: 'config',793 description: 'Validates the compiler configuration options',794 preset: LintRulePreset.Recommended,795 };796 }797 case ErrorCategory.EffectDependencies: {798 return {799 category,800 severity: ErrorSeverity.Error,801 name: 'memoized-effect-dependencies',802 description: 'Validates that effect dependencies are memoized',803 preset: LintRulePreset.Off,804 };805 }806 case ErrorCategory.EffectExhaustiveDependencies: {807 return {808 category,809 severity: ErrorSeverity.Error,810 name: 'exhaustive-effect-dependencies',811 description:812 'Validates that effect dependencies are exhaustive and without extraneous values',813 preset: LintRulePreset.Off,814 };815 }816 case ErrorCategory.EffectDerivationsOfState: {817 return {818 category,819 severity: ErrorSeverity.Error,820 name: 'no-deriving-state-in-effects',821 description:822 'Validates against deriving values from state in an effect',823 preset: LintRulePreset.Off,824 };825 }826 case ErrorCategory.EffectSetState: {827 return {828 category,829 severity: ErrorSeverity.Error,830 name: 'set-state-in-effect',831 description:832 'Validates against calling setState synchronously in an effect. ' +833 'This can indicate non-local derived data, a derived event pattern, or ' +834 'improper external data synchronization.',835 preset: LintRulePreset.Recommended,836 };837 }838 case ErrorCategory.ErrorBoundaries: {839 return {840 category,841 severity: ErrorSeverity.Error,842 name: 'error-boundaries',843 description:844 'Validates usage of error boundaries instead of try/catch for errors in child components',845 preset: LintRulePreset.Recommended,846 };847 }848 case ErrorCategory.FBT: {849 return {850 category,851 severity: ErrorSeverity.Error,852 name: 'fbt',853 description: 'Validates usage of fbt',854 preset: LintRulePreset.Off,855 };856 }857 case ErrorCategory.Gating: {858 return {859 category,860 severity: ErrorSeverity.Error,861 name: 'gating',862 description:863 'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',864 preset: LintRulePreset.Recommended,865 };866 }867 case ErrorCategory.Globals: {868 return {869 category,870 severity: ErrorSeverity.Error,871 name: 'globals',872 description:873 'Validates against assignment/mutation of globals during render, part of ensuring that ' +874 '[side effects must render outside of render](https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',875 preset: LintRulePreset.Recommended,876 };877 }878 case ErrorCategory.Hooks: {879 return {880 category,881 severity: ErrorSeverity.Error,882 name: 'hooks',883 description: 'Validates the rules of hooks',884 /**885 * TODO: the "Hooks" rule largely reimplements the "rules-of-hooks" non-compiler rule.886 * We need to dedeupe these (moving the remaining bits into the compiler) and then enable887 * this rule.888 */889 preset: LintRulePreset.Off,890 };891 }892 case ErrorCategory.Immutability: {893 return {894 category,895 severity: ErrorSeverity.Error,896 name: 'immutability',897 description:898 'Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)',899 preset: LintRulePreset.Recommended,900 };901 }902 case ErrorCategory.Invariant: {903 return {904 category,905 severity: ErrorSeverity.Error,906 name: 'invariant',907 description: 'Internal invariants',908 preset: LintRulePreset.Off,909 };910 }911 case ErrorCategory.PreserveManualMemo: {912 return {913 category,914 severity: ErrorSeverity.Error,915 name: 'preserve-manual-memoization',916 description:917 'Validates that existing manual memoized is preserved by the compiler. ' +918 'React Compiler will only compile components and hooks if its inference ' +919 '[matches or exceeds the existing manual memoization](https://react.dev/learn/react-compiler/introduction#what-should-i-do-about-usememo-usecallback-and-reactmemo)',920 preset: LintRulePreset.Recommended,921 };922 }923 case ErrorCategory.Purity: {924 return {925 category,926 severity: ErrorSeverity.Error,927 name: 'purity',928 description:929 'Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions',930 preset: LintRulePreset.Recommended,931 };932 }933 case ErrorCategory.Refs: {934 return {935 category,936 severity: ErrorSeverity.Error,937 name: 'refs',938 description:939 'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',940 preset: LintRulePreset.Recommended,941 };942 }943 case ErrorCategory.RenderSetState: {944 return {945 category,946 severity: ErrorSeverity.Error,947 name: 'set-state-in-render',948 description:949 'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',950 preset: LintRulePreset.Recommended,951 };952 }953 case ErrorCategory.StaticComponents: {954 return {955 category,956 severity: ErrorSeverity.Error,957 name: 'static-components',958 description:959 'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',960 preset: LintRulePreset.Recommended,961 };962 }963 case ErrorCategory.Suppression: {964 return {965 category,966 severity: ErrorSeverity.Error,967 name: 'rule-suppression',968 description: 'Validates against suppression of other rules',969 preset: LintRulePreset.Off,970 };971 }972 case ErrorCategory.Syntax: {973 return {974 category,975 severity: ErrorSeverity.Error,976 name: 'syntax',977 description: 'Validates against invalid syntax',978 preset: LintRulePreset.Off,979 };980 }981 case ErrorCategory.Todo: {982 return {983 category,984 severity: ErrorSeverity.Hint,985 name: 'todo',986 description: 'Unimplemented features',987 preset: LintRulePreset.Off,988 };989 }990 case ErrorCategory.UnsupportedSyntax: {991 return {992 category,993 severity: ErrorSeverity.Warning,994 name: 'unsupported-syntax',995 description:996 'Validates against syntax that we do not plan to support in React Compiler',997 preset: LintRulePreset.Recommended,998 };999 }1000 case ErrorCategory.UseMemo: {1001 return {1002 category,1003 severity: ErrorSeverity.Error,1004 name: 'use-memo',1005 description:1006 'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',1007 preset: LintRulePreset.Recommended,1008 };1009 }1010 case ErrorCategory.VoidUseMemo: {1011 return {1012 category,1013 severity: ErrorSeverity.Error,1014 name: 'void-use-memo',1015 description:1016 'Validates that useMemos always return a value and that the result of the useMemo is used by the component/hook. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',1017 preset: LintRulePreset.RecommendedLatest,1018 };1019 }1020 case ErrorCategory.MemoDependencies: {1021 return {1022 category,1023 severity: ErrorSeverity.Error,1024 name: 'memo-dependencies',1025 description:1026 'Validates that useMemo() and useCallback() specify comprehensive dependencies without extraneous values. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',1027 /**1028 * TODO: the "MemoDependencies" rule largely reimplements the "exhaustive-deps" non-compiler rule,1029 * allowing the compiler to ensure it does not regress change behavior due to different dependencies.1030 * We previously relied on the source having ESLint suppressions for any exhaustive-deps violations,1031 * but it's more reliable to verify it within the compiler.1032 *1033 * Long-term we should de-duplicate these implementations.1034 */1035 preset: LintRulePreset.Off,1036 };1037 }1038 case ErrorCategory.IncompatibleLibrary: {1039 return {1040 category,1041 severity: ErrorSeverity.Warning,1042 name: 'incompatible-library',1043 description:1044 'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',1045 preset: LintRulePreset.Recommended,1046 };1047 }1048 default: {1049 assertExhaustive(category, `Unsupported category ${category}`);1050 }1051 }1052}10531054export const LintRules: Array<LintRule> = Object.keys(ErrorCategory).map(1055 category => getRuleForCategory(category as any),1056);
Findings
✓ No findings reported for this file.