Rule
no-throw-plain-object (eslint-factory/src/rules/no-throw-plain-object.ts)
Summary
The rule correctly flags throw { ... } object literals, and its diagnostic message prescribes the exact remedy:
Use new Error(message) instead; attach extra fields with Object.assign(new Error(message), { ... }) if needed.
However the rule sets no hasSuggestions and provides no suggestion/fixer. It reports the problem but cannot help apply the fix it recommends. This matters because the flagged population is large and grounded.
Grounding (live corpus)
There are 22 production throw { ... } sites the rule flags today:
actions/setup/js/mcp_server_core.cjs — 10 sites (e.g. lines 721, 737, 749, 757, 765, 776, 781, 791, 801, 817)
actions/setup/js/safe_outputs_handlers.cjs — 12 sites (e.g. lines 292, 1776, 1819, 1888, 1959, 1966, 1973, 1996, 2005, 2066, ...)
All are JSON-RPC 2.0 error objects of the shape:
throw {
code: -32602,
message: "Invalid params: 'name' must be a string",
// sometimes: data: { ... }
};
These lose .stack, are not instanceof Error, and are incompatible with the very error utilities other factory rules exist to protect (getErrorMessage, no-unsafe-catch-error-property, no-json-stringify-error). Converting them to Object.assign(new Error(message), { code, data }) preserves the code/data own-properties that the JSON-RPC transport reads, adds a stack trace, and makes the value Error-compatible — a strict improvement with no consumer-visible regression for code that reads e.code / e.message / e.data.
Proposed refinement
Add meta.hasSuggestions: true and a manual suggestion (not an auto-fix, given transport-serialization nuances) that rewrites:
throw { message: <expr>, code: -32602, data: {...} };
// →
throw Object.assign(new Error(<expr>), { code: -32602, data: {...} });
Behavior to specify with tests:
- Has a
message property → use its value as the new Error(...) argument and drop message from the residual object.
- No
message property → throw Object.assign(new Error(), { ...allProps }); (or new Error() with the full object).
- Empty object
throw {} → suggest throw new Error();.
- Residual becomes empty after removing
message (e.g. throw { message: x }) → suggest throw new Error(x); (skip the empty Object.assign).
- Computed keys, spread elements, shorthand, non-Literal message, method/getter properties → either handle explicitly or skip the suggestion (still report). Do not emit a syntactically-invalid rewrite.
- Preserve source formatting / indentation for multi-line object literals.
Acceptance criteria
no-throw-plain-object sets hasSuggestions: true and emits a suggestion covering cases 1–4 above.
- The suggestion is skipped (report-only) for the unhandled forms in case 5.
- Unit tests cover: with-message, without-message, empty-object, message-only,
data-bearing JSON-RPC object, computed-key skip, spread skip.
- Re-verify against
mcp_server_core.cjs / safe_outputs_handlers.cjs: the suggestion produces valid, behavior-preserving rewrites for the { code, message, data } shape.
Notes
Use a suggestion, not a fixable autofix: some transport code may JSON.stringify the caught value, where an Error's non-enumerable message would be dropped (no-json-stringify-error territory). Keeping it opt-in lets a human confirm the catch/serialize side per call site.
Filed by the ESLint Refiner daily review (grounded, high-impact).
Generated by 🤖 ESLint Refiner · 354.5 AIC · ⌖ 12.7 AIC · ⊞ 4.6K · ◷
Rule
no-throw-plain-object(eslint-factory/src/rules/no-throw-plain-object.ts)Summary
The rule correctly flags
throw { ... }object literals, and its diagnostic message prescribes the exact remedy:However the rule sets no
hasSuggestionsand provides no suggestion/fixer. It reports the problem but cannot help apply the fix it recommends. This matters because the flagged population is large and grounded.Grounding (live corpus)
There are 22 production
throw { ... }sites the rule flags today:actions/setup/js/mcp_server_core.cjs— 10 sites (e.g. lines 721, 737, 749, 757, 765, 776, 781, 791, 801, 817)actions/setup/js/safe_outputs_handlers.cjs— 12 sites (e.g. lines 292, 1776, 1819, 1888, 1959, 1966, 1973, 1996, 2005, 2066, ...)All are JSON-RPC 2.0 error objects of the shape:
These lose
.stack, are notinstanceof Error, and are incompatible with the very error utilities other factory rules exist to protect (getErrorMessage,no-unsafe-catch-error-property,no-json-stringify-error). Converting them toObject.assign(new Error(message), { code, data })preserves thecode/dataown-properties that the JSON-RPC transport reads, adds a stack trace, and makes the value Error-compatible — a strict improvement with no consumer-visible regression for code that readse.code/e.message/e.data.Proposed refinement
Add
meta.hasSuggestions: trueand a manual suggestion (not an auto-fix, given transport-serialization nuances) that rewrites:Behavior to specify with tests:
messageproperty → use its value as thenew Error(...)argument and dropmessagefrom the residual object.messageproperty →throw Object.assign(new Error(), { ...allProps });(ornew Error()with the full object).throw {}→ suggestthrow new Error();.message(e.g.throw { message: x }) → suggestthrow new Error(x);(skip the emptyObject.assign).Acceptance criteria
no-throw-plain-objectsetshasSuggestions: trueand emits a suggestion covering cases 1–4 above.data-bearing JSON-RPC object, computed-key skip, spread skip.mcp_server_core.cjs/safe_outputs_handlers.cjs: the suggestion produces valid, behavior-preserving rewrites for the{ code, message, data }shape.Notes
Use a suggestion, not a
fixableautofix: some transport code mayJSON.stringifythe caught value, where an Error's non-enumerablemessagewould be dropped (no-json-stringify-errorterritory). Keeping it opt-in lets a human confirm the catch/serialize side per call site.Filed by the ESLint Refiner daily review (grounded, high-impact).