compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts TYPESCRIPT 1,057 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.