Skip to content

feat(no-json-stringify-error): scope-aware suggestions + detail-preserving second suggestion#43748

Open
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/eslint-factory-fix-no-json-stringify-error
Open

feat(no-json-stringify-error): scope-aware suggestions + detail-preserving second suggestion#43748
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/eslint-factory-fix-no-json-stringify-error

Conversation

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The no-json-stringify-error rule had a single suggestion (getErrorMessage(err)) that was always emitted regardless of whether getErrorMessage was imported, and it discarded the developer's evident intent to capture more than just .message.

Changes

Scope-aware first suggestion

  • Rule now walks context.sourceCode.getScope(node) up the scope chain to check if getErrorMessage is declared in the file
  • If in scope → useGetErrorMessage: getErrorMessage(err) (existing behavior)
  • If not in scope → useStringFallback: String(err) (safe, no import required)

Detail-preserving second suggestion (always emitted)

  • useDetailPreservingForm (when getErrorMessage in scope): getErrorMessage(err) + "\n" + (err.stack ?? "")
  • useDetailPreservingFormFallback (fallback): String(err) + "\n" + (err.stack ?? "")
  • Uses string concatenation rather than a template literal to avoid nested template literals when the flagged call is already inside a ${...} expression

Example — the live site pattern from the corpus:

// Before (flagged — produces {})
core.error(`  Error details: ${JSON.stringify(error, null, 2)}`);

// Suggestion 1 (concise, getErrorMessage imported)
core.error(`  Error details: ${getErrorMessage(error)}`);

// Suggestion 2 (detail-preserving)
core.error(`  Error details: ${getErrorMessage(error) + "\n" + (error.stack ?? "")}`);

Tests

  • All 11 existing invalid test cases updated (now expect useStringFallback + useDetailPreservingFormFallback since test snippets don't import getErrorMessage)
  • 3 new test cases: CJS in-scope path, destructured-require in-scope path inside a template literal, explicit no-scope fallback

Copilot AI and others added 2 commits July 6, 2026 09:01
…-preserving form

- Add scope check: when `getErrorMessage` is not in scope, fall back to
  `String(errorVar)` (useStringFallback) instead of an unresolved identifier
- Add second detail-preserving suggestion that emits
  `\`\${getErrorMessage(errorVar)}\\n\${errorVar.stack ?? ""}\`` (or
  `String()` variant when out of scope) so callers who wanted null,2-style
  detail retain the stack trace
- Add new message IDs: useStringFallback, useDetailPreservingForm,
  useDetailPreservingFormFallback
- Update all existing invalid test cases to reflect the new two-suggestion shape
- Add three new test cases: in-scope getErrorMessage path (CJS require),
  in-scope path inside template literal, and explicit String() fallback

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…serving suggestion

Replace template-literal approach with string concatenation to avoid
nested template literals when the flagged call is already inside a
template expression.  The detail-preserving suggestions now produce:
  getErrorMessage(err) + "\n" + (err.stack ?? "")   (in-scope path)
  String(err) + "\n" + (err.stack ?? "")             (fallback path)

Both forms are valid in any syntactic context — standalone arguments,
template expressions, and ternaries — without requiring template nesting.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix eslint-factory no-json-stringify-error suggestion duplication feat(no-json-stringify-error): scope-aware suggestions + detail-preserving second suggestion Jul 6, 2026
Copilot AI requested a review from pelikhan July 6, 2026 09:11
@pelikhan pelikhan marked this pull request as ready for review July 6, 2026 09:41
Copilot AI review requested due to automatic review settings July 6, 2026 09:41
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the no-json-stringify-error ESLint rule to emit scope-aware suggestions: it now prefers getErrorMessage(err) only when getErrorMessage is resolvable in the current scope chain, and otherwise suggests an import-free fallback (String(err)). It also adds a second “detail-preserving” suggestion intended to retain stack information while avoiding nested template literals.

Changes:

  • Add scope-chain inspection to choose between getErrorMessage(err) and String(err) for the first suggestion.
  • Add an always-emitted second suggestion that preserves additional detail (stack).
  • Update existing tests and add new cases for in-scope CommonJS patterns and fallback behavior.
Show a summary per file
File Description
eslint-factory/src/rules/no-json-stringify-error.ts Adds scope-aware suggestion selection and introduces a second, detail-preserving suggestion.
eslint-factory/src/rules/no-json-stringify-error.test.ts Updates expected suggestions/outputs and adds new test cases for in-scope detection.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 4
  • Review effort level: Low

Comment on lines +133 to +140
const getErrorMessageInScope = (() => {
let scope: ReturnType<typeof context.sourceCode.getScope> | null = context.sourceCode.getScope(node);
while (scope) {
if (scope.variables.some(v => v.name === "getErrorMessage")) return true;
scope = scope.upper;
}
return false;
})();
Comment on lines +52 to +53
useDetailPreservingForm: 'Replace with getErrorMessage({{errorVar}}) + "\\n" + ({{errorVar}}.stack ?? "") to preserve the stack trace — ensure getErrorMessage is imported from error_helpers.cjs.',
useDetailPreservingFormFallback: 'Replace with String({{errorVar}}) + "\\n" + ({{errorVar}}.stack ?? "") to preserve the stack trace.',
messageId: "useDetailPreservingForm" as const,
data: { errorVar },
fix(fixer) {
return fixer.replaceText(node, `getErrorMessage(${errorVar}) + "\\n" + (${errorVar}.stack ?? "")`);
messageId: "useDetailPreservingFormFallback" as const,
data: { errorVar },
fix(fixer) {
return fixer.replaceText(node, `String(${errorVar}) + "\\n" + (${errorVar}.stack ?? "")`);
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 3 test(s): 3 design, 0 implementation, 0 violation(s).

📊 Metrics (3 tests)
Metric Value
Analyzed 3 (Go: 0, JS/TS: 3)
✅ Design 3 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (67%)
Duplicate clusters 0
Inflation YES (164 test lines / 46 prod lines = 3.6×)
🚨 Violations 0

Note: The pre-fetch test-detection scripts (targeting *_test.go and *.test.cjs/*.test.js) did not capture no-json-stringify-error.test.ts. Tests were analyzed directly from the branch diff.

Test File Classification Issues
invalid: when getErrorMessage is in scope, suggests getErrorMessage and detail-preserving form no-json-stringify-error.test.ts:390 design_test / behavioral_contract / high_value None
invalid: when getErrorMessage is in scope via destructured require, detail-preserving form uses getErrorMessage no-json-stringify-error.test.ts:419 design_test / behavioral_contract / high_value None
invalid: when getErrorMessage is not in scope, first suggestion uses String() fallback no-json-stringify-error.test.ts:448 design_test / behavioral_contract / high_value None

Verdict

Passed. 0% implementation tests (threshold: 30%). All 3 new tests are design tests verifying the scope-aware suggestion-selection invariant.

Inflation note: The 3.6× ratio (164 added test lines vs 46 production lines) is above the 2:1 threshold, but is structurally expected for ESLint RuleTester tests — each test case must specify complete code input and exact transformed output for every suggestion. The bulk of test additions are updates to existing cases to assert the second (detail-preserving) suggestion, which are necessary regression coverage for the new feature.

Test breakdown:

  • 3 new tests cover the core design invariant: scope-aware first-suggestion selection (getErrorMessage in scope via direct property access, via destructuring, and absent/fallback to String()).
  • Modified tests (10+ existing cases) updated to assert both suggestions now provided by the rule — these are correct regression guards.

References: §28782440159

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 36.3 AIC · ⌖ 11.4 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 80/100. 0% implementation tests (threshold: 30%). All 3 new tests are design tests verifying the scope-aware suggestion-selection invariant. Inflation ratio (3.6×) is structurally justified by ESLint RuleTester verbosity.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /tdd, /codebase-design, and /grill-with-docs — COMMENT with 4 targeted suggestions.

📋 Key Themes & Highlights

Key Themes

  • Missing ESM in-scope test: All three new scope-aware tests use cjsRuleTester. The ESM import path is exercised nowhere, leaving a gap where a regression in ESM scope detection would go unnoticed. (See inline comment on line 391 of the test file.)
  • Scope check precision: scope.variables.some(v => v.name === "getErrorMessage") matches any declaration of that name — including let getErrorMessage = null or a forward-declared-but-uninitialized var — potentially surfacing useGetErrorMessage when the call would fail at runtime. Adding v.defs.length > 0 would tighten this. (See inline comment on line 136 of the rule file.)
  • Undocumented local-scope behavior: A const getErrorMessage = ... declared inside the catch body would be found by the upward scope walk. Whether that's intentional is undocumented by a test. (See inline comment on line 133.)
  • Primary error message inconsistency: The jsonStringifyError message hardcodes "Prefer getErrorMessage" even when the rule now chooses String() as the first suggestion. (See inline comment on line 52.)

Positive Highlights

  • ✅ Scope-chain traversal is clean and idiomatic — using context.sourceCode.getScope(node) with scope.upper is exactly the right approach for ESLint scope analysis.
  • ✅ String concatenation instead of template literals for the detail-preserving suggestion is a good defensive choice — avoids nested template literal issues when the flagged expression is already inside a ${...}.
  • ✅ 11 existing tests updated to match new behavior; the dual-suggestion structure is consistent and clear across all cases.
  • ✅ Three new test cases cleanly document the two new code paths (in-scope CJS, destructured require, explicit no-scope fallback).

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 82.4 AIC · ⌖ 5.93 AIC · ⊞ 6.7K
Comment /matt to run again

});

it("invalid: when getErrorMessage is in scope, suggests getErrorMessage and detail-preserving form", () => {
cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] No ESM in-scope test: all three new test cases use cjsRuleTester. The ESM import path (import { getErrorMessage } from ...) is untested — scope analysis for import declarations differs from CJS require() and is worth an explicit esmRuleTester case.

💡 Suggested test addition
it("invalid: when getErrorMessage is imported via ESM, first suggestion uses getErrorMessage", () => {
  esmRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, {
    valid: [],
    invalid: [
      {
        code: `import { getErrorMessage } from "./error_helpers"; try { f(); } catch (err) { core.error(JSON.stringify(err)); }`,
        errors: [{
          messageId: "jsonStringifyError",
          data: { errorVar: "err" },
          suggestions: [{
            messageId: "useGetErrorMessage",
            data: { errorVar: "err" },
            output: `import { getErrorMessage } from "./error_helpers"; try { f(); } catch (err) { core.error(getErrorMessage(err)); }`,
          }],
        }],
      },
    ],
  });
});

Without this, a regression in ESM scope detection would go unnoticed.

@copilot please address this.

const errorVar = firstArg.name;

// Check whether getErrorMessage is resolvable in the current scope chain.
const getErrorMessageInScope = (() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] No test for getErrorMessage declared inside the catch block itself. The scope walk starts at context.sourceCode.getScope(node) (the CallExpression) and walks upward, which means a local const getErrorMessage = ... inside the catch body would be found. That's probably the right behavior, but there's no test confirming it — or ruling it out.

💡 Why this matters

Consider:

try { f(); } catch (err) {
  const getErrorMessage = customHelper;
  core.error(JSON.stringify(err)); // would this pick useGetErrorMessage or useStringFallback?
}

Add a test to document the intended behavior (accept useGetErrorMessage here, or document why it shouldn't).

@copilot please address this.

const getErrorMessageInScope = (() => {
let scope: ReturnType<typeof context.sourceCode.getScope> | null = context.sourceCode.getScope(node);
while (scope) {
if (scope.variables.some(v => v.name === "getErrorMessage")) return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] scope.variables.some(v => v.name === "getErrorMessage") matches any variable named getErrorMessage — including uninitialized, re-assigned, or completely unrelated variables like let getErrorMessage = null. This could cause the rule to emit useGetErrorMessage when calling getErrorMessage(err) would throw at runtime.

💡 Consider checking for at least one write reference (i.e., an actual binding)
// More precise: require at least one write reference so that uninitialized declarations don't count
if (scope.variables.some(v =>
  v.name === "getErrorMessage" && v.defs.length > 0
)) return true;

v.defs.length > 0 ensures the variable has an actual definition (import, const, function) rather than only being an identifier reference.

@copilot please address this.

"JSON.stringify({{errorVar}}) produces {} for Error objects — Error properties (message, stack, etc.) are non-enumerable. Prefer getErrorMessage({{errorVar}}) from error_helpers.cjs or explicitly serialize a guarded value after narrowing it.",
useGetErrorMessage: "Replace with getErrorMessage({{errorVar}}) — ensure getErrorMessage is imported from error_helpers.cjs.",
useStringFallback: "Replace with String({{errorVar}}) — getErrorMessage is not in scope; String() is a safe import-free alternative.",
useDetailPreservingForm: 'Replace with getErrorMessage({{errorVar}}) + "\\n" + ({{errorVar}}.stack ?? "") to preserve the stack trace — ensure getErrorMessage is imported from error_helpers.cjs.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/grill-with-docs] The jsonStringifyError primary message still says "Prefer getErrorMessage({{errorVar}})" unconditionally, even though the rule now sometimes suggests String() as the first option. The message misleads users in the no-scope case.

💡 Suggested fix

Consider splitting into two message variants, or make the primary message scope-neutral:

jsonStringifyError:
  "JSON.stringify({{errorVar}}) produces {} for Error objects — Error properties are non-enumerable. " +
  "Use the suggestions below to replace it with a safe alternative.",

This avoids confusing users who don't have getErrorMessage imported.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: no-json-stringify-error scope-aware suggestions

The scope-walk logic and dual-suggestion approach are well-structured and the bulk of test coverage is solid. Two actionable issues found:

1. Diagnostic message contradicts the fallback suggestion (blocking)

When getErrorMessage is not in scope, the rule still emits the jsonStringifyError messageId whose text reads "Prefer getErrorMessage({{errorVar}}) from error_helpers.cjs". The first suggestion then proposes String(err) — directly contradicting the message body. The main error message should be updated to scope-neutral wording, or a second messageId (e.g. jsonStringifyErrorNoHelper) should be registered and emitted when the fallback path is taken. See inline comment on no-json-stringify-error.ts line 49.

2. Missing ESM import scope test (non-blocking but recommended)

All three new scope-aware test cases use cjsRuleTester with require() patterns. The scope walk reads scope.variables, which also covers ES module import { getErrorMessage } from '...' bindings — but that code path has no test. A regression in ESM import detection would be invisible without one. See inline comment on the new test block in no-json-stringify-error.test.ts.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 82.1 AIC · ⌖ 6.15 AIC · ⊞ 4.9K

@@ -48,6 +48,9 @@ export const noJsonStringifyErrorRule = createRule({
jsonStringifyError:
"JSON.stringify({{errorVar}}) produces {} for Error objects — Error properties (message, stack, etc.) are non-enumerable. Prefer getErrorMessage({{errorVar}}) from error_helpers.cjs or explicitly serialize a guarded value after narrowing it.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The jsonStringifyError diagnostic message always says "Prefer getErrorMessage({{errorVar}}) from error_helpers.cjs" regardless of whether getErrorMessage is in scope. When the scope check yields false, the first suggestion becomes String(err) — but the diagnostic still points the developer at getErrorMessage. The two signals disagree.

Consider a scope-neutral phrasing, e.g.:

"JSON.stringify({{errorVar}}) produces {} for Error objects — ... Use getErrorMessage({{errorVar}}) if it is imported, or String({{errorVar}}) as a safe import-free alternative."

Alternatively, register a second messageId (e.g. jsonStringifyErrorNoHelper) and emit it when getErrorMessageInScope is false.

@copilot please address this.

});

it("invalid: when getErrorMessage is in scope, suggests getErrorMessage and detail-preserving form", () => {
cjsRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The three new "in scope" test cases use cjsRuleTester with CJS require() patterns. The scope-walk (context.sourceCode.getScope(node).variables) should also detect ES module import { getErrorMessage } from "..." bindings — but there is no test covering this path.

Adding a test with esmRuleTester:

it("invalid: when getErrorMessage is imported (ESM), suggests getErrorMessage", () => {
  esmRuleTester.run("no-json-stringify-error", noJsonStringifyErrorRule, {
    valid: [],
    invalid: [
      {
        code: `import { getErrorMessage } from "./error_helpers.js"; try { f(); } catch (err) { core.error(JSON.stringify(err)); }`,
        errors: [
          {
            messageId: "jsonStringifyError",
            data: { errorVar: "err" },
            suggestions: [
              { messageId: "useGetErrorMessage", data: { errorVar: "err" },
                output: `import { getErrorMessage } from "./error_helpers.js"; try { f(); } catch (err) { core.error(getErrorMessage(err)); }` },
              { messageId: "useDetailPreservingForm", data: { errorVar: "err" },
                output: \`import { getErrorMessage } from "./error_helpers.js"; try { f(); } catch (err) { core.error(getErrorMessage(err) + "\\n" + (err.stack ?? "")); }\` },
            ],
          },
        ],
      },
    ],
  });
});

Without this, a regression in ESM scope detection would go unnoticed.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

REQUEST_CHANGES — two correctness bugs in the fixer plus four test coverage gaps.

### Blocking issues (must fix before merge)

1. Fixer missing parentheses (high) — The detail-preserving fixers replace the raw JSON.stringify(err) node with getErrorMessage(err) + "\n" + (err.stack ?? "") without wrapping in parentheses. When the original call is chained (.slice(), .trim(), etc.) the replacement silently changes runtime semantics — the chained method attaches only to (err.stack ?? "") instead of the whole expression. Both useDetailPreservingForm and useDetailPreservingFormFallback fixers are affected.

2. TDZ false positive (high)scope.variables is populated by ESLint for all const/let bindings in a block regardless of textual order. If getErrorMessage is declared after the JSON.stringify(err) call in the same block, the scope walk returns true and the rule suggests getErrorMessage(err), which throws a ReferenceError at runtime due to the temporal dead zone. A range check against node.range[0] is needed.

### Non-blocking issues (should fix)

3. Name-only scope detection (medium) — Any binding named getErrorMessage — a local function, test mock, different library — triggers the useGetErrorMessage path and tells the developer to "ensure getErrorMessage is imported from error_helpers.cjs", which is incorrect when a different getErrorMessage is already present.

4. Detail-preserving suggestion duplicates the message (medium)String(err) + "\n" + (err.stack ?? "") produces the error message twice on most engines, since err.stack already starts with "Error: message". (err.stack ?? String(err)) is a cleaner idiom.

5. Missing ESM in-scope test (medium)esmRuleTester only covers the fallback path. An ESM import test is needed to confirm the scope walk traverses module scope correctly.

6. Missing promise-handler in-scope test (medium) — No test verifies that getErrorMessage is correctly detected when the call site is inside a .catch()/.then() handler, where the custom sentinel-frame mechanism and the ESLint scope API interact.

🔎 Code quality review by PR Code Quality Reviewer · 126.1 AIC · ⌖ 5.99 AIC · ⊞ 5.5K
Comment /review to run again

Comments that could not be inline-anchored

eslint-factory/src/rules/no-json-stringify-error.ts:169

Fixer output missing parentheses breaks operator precedence when JSON.stringify(err) is part of a chained call — applying suggestion 2 silently produces wrong code.

<details>
<summary>💡 Explanation and fix</summary>

fixer.replaceText(node, ...) replaces only the JSON.stringify(err) CallExpression node. If the original code chains a method on it:

console.log(JSON.stringify(err).slice(0, 200))

the replacement emits:

// WRONG — .slice(0, 200) applies only to (err.…

</details>

<details><summary>eslint-factory/src/rules/no-json-stringify-error.ts:134</summary>

**TDZ false positive: `const`/`let getErrorMessage` declared after the call site still triggers `useGetErrorMessage`** — the suggestion emits code that throws `ReferenceError` at runtime.

&lt;details&gt;
&lt;summary&gt;💡 Explanation and fix&lt;/summary&gt;

ESLint&#39;s `scope.variables` contains **all** `const`/`let` bindings for a block scope, regardless of their textual position. If user code looks like:

```js
try { f(); } catch (err) {
  core.error(JSON.stringify(err)); // flagged here
  const getErrorMessage…

</details>

<details><summary>eslint-factory/src/rules/no-json-stringify-error.ts:136</summary>

**Scope check matches any identifier named `getErrorMessage`, not just the `error_helpers.cjs` import**  a local function, test mock, or different library triggers the `useGetErrorMessage` path with a misleading message.

&lt;details&gt;
&lt;summary&gt;💡 Explanation&lt;/summary&gt;

The check `v.name === &quot;getErrorMessage&quot;` is purely name-based. Consider:

```js
function getErrorMessage(e) { return e.code; } // totally unrelated helper
try { f(); } catch (err) {
  core.error(JSON.stringify(err));
}

The rul…

eslint-factory/src/rules/no-json-stringify-error.ts:176

String(err) + &quot;\\n&quot; + (err.stack ?? &quot;&quot;) duplicates the error message — the suggested fix produces noisier output than the original intent.

<details>
<summary>💡 Detail</summary>

String(err) on a standard Error produces &quot;Error: the message&quot;. On most JS engines err.stack starts with the same &quot;Error: the message&quot; line before the frame list:

Error: the message
    at f (file.js:1:5)
    at ...

So the generated code:

console.error(String(err) + &quot;\n&quot; + (err.stack 

</details>

<details><summary>eslint-factory/src/rules/no-json-stringify-error.test.ts:312</summary>

**No ESM test with `getErrorMessage` in scope**  the `esmRuleTester` block only covers the fallback path, leaving the scope-detection logic untested for ES module files.

&lt;details&gt;
&lt;summary&gt;💡 Detail&lt;/summary&gt;

All three new tests that exercise `useGetErrorMessage` / `useDetailPreservingForm` use `cjsRuleTester` with a `require()` call to introduce the binding. ESM `import` declarations produce a different scope structure in ESLint (module-level scope vs. program scope). A test like:

```ts
es…

</details>

<details><summary>eslint-factory/src/rules/no-json-stringify-error.test.ts:390</summary>

**No test for `getErrorMessage`-in-scope inside a `.catch()` or `.then()` rejection handler** — the scope-detection logic&#39;s interaction with the custom sentinel-frame stack is untested for promise handlers.

&lt;details&gt;
&lt;summary&gt;💡 Detail&lt;/summary&gt;

The rule uses its own `scopeStack` with sentinels to track caught error variables, while the new scope-detection feature uses ESLint&#39;s built-in `context.sourceCode.getScope(node)` API. These are two separate mechanisms. Inside an inline `.catch(err =&gt;

</details>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants