Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,35 @@ export const TestRecommendedRules: Rule.RuleModule = {
schema: [{type: 'object', additionalProperties: true}],
},
create(context) {
// Aggregate all listeners from recommended rules
type ListenerFunction = (node: Rule.Node) => void;
const aggregatedListeners: Record<string, ListenerFunction[]> = {};

for (const ruleConfig of Object.values(
configs.recommended.plugins['react-compiler'].rules,
)) {
const listener = ruleConfig.rule.create(context);
if (Object.entries(listener).length !== 0) {
throw new Error('TODO: handle rules that return listeners to eslint');

// Aggregate listeners by their event type (e.g., 'Program', 'CallExpression')
for (const [eventType, handler] of Object.entries(listener)) {
if (!aggregatedListeners[eventType]) {
aggregatedListeners[eventType] = [];
}
aggregatedListeners[eventType].push(handler as ListenerFunction);
}
}
return {};

// Create combined listeners that call all handlers for each event type
const combinedListeners: Rule.RuleListener = {};
for (const [eventType, handlers] of Object.entries(aggregatedListeners)) {
combinedListeners[eventType] = (node: Rule.Node) => {
for (const handler of handlers) {
handler(node);
}
};
}

return combinedListeners;
},
};

Expand Down
42 changes: 40 additions & 2 deletions compiler/packages/react-compiler-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,46 @@ export function $reset($: MemoCache) {
}
}

export function $makeReadOnly() {
throw new Error('TODO: implement $makeReadOnly in react-compiler-runtime');
/**
* Wraps a value to make it read-only in development mode.
* This is used for debugging purposes to detect mutations to values
* that the compiler assumes are immutable.
*
* In production, this is a no-op that returns the value as-is.
* In development, this freezes objects to catch mutations.
*
* Note: A more sophisticated implementation using proxies exists in the
* make-read-only-util package, but this simpler version suffices for
* runtime usage where we just need to catch obvious mutations.
*
* Limitations:
* - Only performs shallow freeze (Object.freeze). Nested object properties
* can still be mutated. A deep freeze would be more correct but adds
* runtime overhead and complexity.
* - Does not freeze React elements to avoid breaking React internals.
* - Silently ignores objects that cannot be frozen (e.g., some host objects).
*
* @param value The value to make read-only
* @returns The same value (frozen in development mode)
*/
export function $makeReadOnly<T>(value: T): T {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

$makeReadOnly signature doesn't match compiler-emitted calls

The compiler emits calls with two arguments: makeReadOnly(value, "ComponentName") (as seen in test fixtures like codegen-emit-make-read-only.expect.md). This function only accepts one parameter (value: T), so the second source argument (the component/hook name used for diagnostic messages) is silently ignored.

While this doesn't cause a runtime error (extra JS arguments are simply dropped), it means the implementation loses diagnostic context that the proxy-based version in make-read-only-util uses for its violation logging. Consider adding the source parameter for consistency:

Suggested change
export function $makeReadOnly<T>(value: T): T {
export function $makeReadOnly<T>(value: T, _source?: string): T {
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/react-compiler-runtime/src/index.ts
Line: 214

Comment:
**`$makeReadOnly` signature doesn't match compiler-emitted calls**

The compiler emits calls with two arguments: `makeReadOnly(value, "ComponentName")` (as seen in test fixtures like `codegen-emit-make-read-only.expect.md`). This function only accepts one parameter `(value: T)`, so the second `source` argument (the component/hook name used for diagnostic messages) is silently ignored.

While this doesn't cause a runtime error (extra JS arguments are simply dropped), it means the implementation loses diagnostic context that the proxy-based version in `make-read-only-util` uses for its violation logging. Consider adding the `source` parameter for consistency:

```suggestion
export function $makeReadOnly<T>(value: T, _source?: string): T {
```

How can I resolve this? If you propose a fix, please make it concise.

if (
typeof value === 'object' &&
value !== null &&
!isValidElement(value) &&
!Object.isFrozen(value)
) {
// Freeze the object to catch mutations in development.
// In production builds, this code path is typically eliminated
// by dead code elimination when __DEV__ checks are removed.
try {
Object.freeze(value);
} catch (e) {
// Some objects cannot be frozen (e.g., certain host objects)
// Silently ignore errors to avoid breaking the application
}
}
return value;
}
Comment on lines +214 to 232

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

$makeReadOnly freezes unconditionally in production

The comment on line 222-223 says "In production builds, this code path is typically eliminated by dead code elimination when __DEV__ checks are removed." However, there is no __DEV__ guard in this implementation β€” Object.freeze will run in all environments.

By contrast, the existing makeReadOnly (from make-read-only-util, the Proxy-based implementation) is always emitted inside a __DEV__ ternary by the compiler (e.g., $[2] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;). This function has no such guard and will freeze objects in production, which could cause subtle breakage in applications that rely on mutating values returned from compiled components.

If this is intended to be dev-only, it should either:

  1. Wrap the freeze in a __DEV__ check, or
  2. Remove the misleading comment about dead code elimination
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/react-compiler-runtime/src/index.ts
Line: 214:232

Comment:
**`$makeReadOnly` freezes unconditionally in production**

The comment on line 222-223 says "In production builds, this code path is typically eliminated by dead code elimination when `__DEV__` checks are removed." However, there is no `__DEV__` guard in this implementation β€” `Object.freeze` will run in all environments.

By contrast, the existing `makeReadOnly` (from `make-read-only-util`, the Proxy-based implementation) is always emitted inside a `__DEV__` ternary by the compiler (e.g., `$[2] = __DEV__ ? makeReadOnly(y, "MyComponentName") : y;`). This function has no such guard and will freeze objects in production, which could cause subtle breakage in applications that rely on mutating values returned from compiled components.

If this is intended to be dev-only, it should either:
1. Wrap the freeze in a `__DEV__` check, or
2. Remove the misleading comment about dead code elimination

How can I resolve this? If you propose a fix, please make it concise.


/**
Expand Down
Loading
Loading