mui-public icon indicating copy to clipboard operation
mui-public copied to clipboard

[code-infra] Add ESLint rules to enforce tree-shakeable production guards for dev-only functions

Open Copilot opened this issue 4 months ago • 10 comments

  • [x] Understand the issue requirements
  • [x] Set up development environment (Node 22, pnpm)
  • [x] Install dependencies and build packages
  • [x] Run existing tests to verify baseline (all pass)
  • [x] Create new ESLint rule: require-dev-wrapper
  • [x] Implement rule logic to check for production conditional wrapper
  • [x] Create comprehensive test suite for the rule (14 tests)
  • [x] Register the rule in the plugin index
  • [x] Run all tests (1371 tests pass including 14 new ones)
  • [x] Run TypeScript checks (pass)
  • [x] Run ESLint (pass)
  • [x] Run prettier (pass)
  • [x] Add comprehensive documentation and examples
  • [x] Address feedback: Update rule to accept both === and !== comparisons
  • [x] Address feedback: Split into two separate rules
  • [x] Address feedback: DRY up code with shared utilities
  • [x] Address feedback: Fix else block handling
  • [x] Address feedback: Simplify branch checking logic
  • [x] Address feedback: Remove recursive check (binary comparisons only)
  • [x] Address feedback: Refactor isNodeEnvComparison signature and logic
  • [x] Address feedback: Move isNodeEnvComparison and simplify branch detection
  • [x] Address feedback: Fix to only accept tree-shakeable patterns
  • [x] Add test case for nested if statements in else block
  • [x] Final validation complete

Implementation Summary

Created two ESLint rules to enforce production guards with shared utilities:

Shared Utilities (nodeEnvUtils.mjs)

  • isProcessEnvNodeEnv(node): Checks if a node is process.env.NODE_ENV
  • isLiteral(node, value): Checks if a node is a Literal with specific value
  • Reduces code duplication between rules

1. material-ui/require-dev-wrapper

  • Enforces that certain function calls (warnOnce, warn, checkSlot) must be wrapped with tree-shakeable production checks
  • Only accepts patterns that tree-shake in production:
    • if (process.env.NODE_ENV !== 'production') { devCode() } - tree-shakes
    • if (process.env.NODE_ENV === 'development') { devCode() } - tree-shakes
    • if (process.env.NODE_ENV === 'production') {} else { devCode() } - tree-shakes
    • if (process.env.NODE_ENV === 'production') { devCode() } - does NOT tree-shake!
    • if (process.env.NODE_ENV !== 'test') { devCode() } - does NOT tree-shake reliably
  • Uses clean logic with currentChild variable for efficient branch detection
  • Properly handles nested if statements in else blocks
  • Only validates binary comparisons (no recursive node traversal)
  • Configurable via options to customize the list of function names
  • 19 comprehensive tests (including nested scenarios)

2. material-ui/consistent-production-guard

  • Enforces consistent patterns for ALL production guard checks using process.env.NODE_ENV
  • Only allows comparison with the literal string 'production'
  • Rejects non-statically analyzable constructs (e.g., comparing with variables)
  • Ensures process.env.NODE_ENV is only used in binary comparisons with === or !==
  • Uses checkGuard helper to DRY up left/right comparison logic
  • 13 comprehensive tests

These two rules work together but can be enabled independently:

  • require-dev-wrapper ensures critical dev functions are guarded
  • consistent-production-guard ensures all production guards follow best practices

The rules help prevent development-only utilities from adding weight to production bundles, addressing issue #207.

Original prompt

@mui/mui-public/issues/207


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot avatar Oct 07 '25 11:10 Copilot