Skip to content

feat(linter): implement eslint-plugin-react-refresh/only-export-components rule with full configuration support #12756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Aug 2, 2025

This PR implements the only-export-components rule from eslint-plugin-react-refresh with comprehensive configuration support to ensure files only export React components for proper Fast Refresh/Hot Module Replacement functionality.

What this rule does

The rule validates that files containing React components follow patterns that work correctly with Fast Refresh:

  • Prevents export-all declarations: export * from "./foo" cannot be verified to only export components
  • Requires named exports: Anonymous function exports like export default function() {} break Fast Refresh
  • Enforces component-only exports: Mixing component and non-component exports prevents proper hot reloading
  • Detects local components: Components that exist but aren't exported should either be exported or moved to separate files
  • Separates React contexts: Context objects should be in separate files from components

Configuration Options

The rule supports all configuration options from the original eslint-plugin-react-refresh:

  • allowConstantExport: Allow exporting constants alongside components
  • allowExportNames: Allow specific export names (array of strings)
  • customHOCs: Support for custom Higher-Order Components (array of strings)
  • checkJS: Support for JSX in .js files (extends beyond just .jsx/.tsx)

Enhanced Features

  • Component Name Detection: Supports underscores in component names (My_Component)
  • Comprehensive HOC Support: Built-in support for memo, forwardRef, connect, withRouter, observer, plus custom HOCs
  • Class Components: Full detection and support for React class components
  • Function Expressions: Detects function expressions as components
  • React Context Detection: Identifies createContext() and React.createContext() calls
  • Type-only Exports: Properly handles TypeScript type-only exports
  • Enhanced File Detection: Recognizes test files in __tests__ directories and various test file patterns

Examples

❌ Incorrect - Mixed exports break Fast Refresh:

export const CONSTANT = 3;
export const Component = () => <></>;

✅ Correct with allowConstantExport - Constants allowed with configuration:

// With { "allowConstantExport": true }
export const CONSTANT = 3;
export const Component = () => <></>;

✅ Correct - Component-only exports work with Fast Refresh:

export default function Component() {
  return <></>;
}

export const AnotherComponent = memo(() => <></>);

The implementation follows oxc's patterns and integrates seamlessly with the existing React plugin, providing full compatibility with projects migrating from ESLint to OXC.

Fixes #8698.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@Copilot Copilot AI assigned Copilot and Boshen Aug 2, 2025
Copy link
Contributor

graphite-app bot commented Aug 2, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

Copilot AI and others added 2 commits August 2, 2025 13:24
Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
…nents rule

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] linter: support eslint-plugin-react-refresh feat(linter): implement eslint-plugin-react-refresh/only-export-components rule Aug 2, 2025
@Copilot Copilot AI requested a review from Boshen August 2, 2025 13:29
Copilot finished work on behalf of Boshen August 2, 2025 13:29
@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI C-enhancement Category - New feature or request labels Aug 2, 2025
Copy link

codspeed-hq bot commented Aug 2, 2025

CodSpeed Instrumentation Performance Report

Merging #12756 will not alter performance

Comparing copilot/fix-8698 (f23ece7) with main (e760fd4)

Summary

✅ 34 untouched benchmarks

Copy link
Member

@Dunqing Dunqing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Implementation needs improvements

Thank you for working on implementing the only-export-components rule! After reviewing the implementation against the original eslint-plugin-react-refresh, I've identified several areas that need attention:

Missing Features

  1. Configuration Options: The implementation is missing all configuration options from the original plugin:

    • allowConstantExport: Allow exporting constants alongside components
    • allowExportNames: Allow specific export names
    • customHOCs: Support for custom Higher-Order Components
    • checkJS: Support for JSX in .js files
  2. Limited HOC Support: Currently only supports memo and forwardRef, but missing:

    • connect from react-redux
    • withRouter from react-router
    • Other common HOCs
    • Custom HOCs via configuration

Implementation Issues

  1. Component Name Detection (line 358-360): The regex is too restrictive:

    fn is_react_component_name(&self, name: &str) -> bool {
        name.chars().next().map_or(false, |c| c.is_ascii_uppercase())
            && name.chars().all(|c| c.is_ascii_alphanumeric())
    }

    This doesn't allow underscores in component names (e.g., My_Component).

  2. Missing Component Types:

    • No detection of class components
    • No support for function expressions as components
    • Missing type-only export handling (TypeScript)

Test Coverage

  1. Insufficient Test Cases: The current tests don't cover:
    • HOC scenarios
    • Mixed exports with constants
    • React context exports
    • Type-only exports
    • Configuration options
    • Class components
    • Nested HOCs

Other Considerations

  1. File Detection: The test file detection might miss patterns like:

    • Files in __tests__ directories
    • Files ending with .test.js (without dot before "test")
  2. Missing Edge Cases:

    • No handling of nested HOC calls
    • No detection of all-uppercase exports (e.g., const CMS = () => {})

Recommendation

Consider expanding the implementation to match the original plugin's functionality more closely. The core logic is good, but it needs these additional features to be fully compatible with projects migrating from ESLint to OXC.

Would you like help implementing any of these missing features?

…configuration options and better detection

Co-authored-by: Dunqing <29533304+Dunqing@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Aug 2, 2025

Review: Implementation needs improvements

Thank you for working on implementing the only-export-components rule! After reviewing the implementation against the original eslint-plugin-react-refr...

I've significantly enhanced the implementation to address all the missing features you identified:

Fixed Issues

Component Name Detection: Fixed the regex to allow underscores (My_Component now works)

Configuration Options: Added all missing options:

  • allowConstantExport: Allow constants alongside components
  • allowExportNames: Allow specific export names
  • customHOCs: Support for custom Higher-Order Components
  • checkJS: Support for JSX in .js files

Enhanced HOC Support: Now supports connect, withRouter, observer, React.memo, React.forwardRef, plus custom HOCs

Better Component Detection: Added class components, function expressions, anonymous export detection

Improved File Detection: Now handles __tests__ directories and .test.js patterns

React Context Detection: Proper handling of createContext() and React.createContext()

Type-only Exports: Correctly skips TypeScript type-only exports

Comprehensive Testing: Added test coverage for all new features and configuration options

The implementation now closely matches the original eslint-plugin-react-refresh functionality while following oxc's patterns. All tests pass and clippy warnings are resolved.

Commit: f23ece7

@Copilot Copilot AI changed the title feat(linter): implement eslint-plugin-react-refresh/only-export-components rule feat(linter): implement eslint-plugin-react-refresh/only-export-components rule with full configuration support Aug 2, 2025
@Copilot Copilot AI requested a review from Dunqing August 2, 2025 14:21
Copilot finished work on behalf of Dunqing August 2, 2025 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cli Area - CLI A-linter Area - Linter C-enhancement Category - New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

linter: support eslint-plugin-react-refresh
3 participants