Skip to content

fix(eslint-factory): prefer-core-logging suggestion unsafe for non-string arguments#47020

Merged
pelikhan merged 4 commits into
mainfrom
copilot/eslint-factory-fix-autofix-suggestion
Jul 21, 2026
Merged

fix(eslint-factory): prefer-core-logging suggestion unsafe for non-string arguments#47020
pelikhan merged 4 commits into
mainfrom
copilot/eslint-factory-fix-autofix-suggestion

Conversation

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

canSuggestCoreReplacement offered the replaceWithCoreMethod autofix for any single-argument console.* call, including identifiers, object literals, and numeric literals. This silently degrades output: console.log(obj) uses util.inspect, but core.info(obj) coerces to "[object Object]".

Changes

  • canSuggestCoreReplacement — gates the suggestion on getStaticStringValue(arg) !== null, restricting it to string Literal and no-expression TemplateLiteral nodes. All other argument types (identifiers, objects, numbers) produce a report with no suggestion.
  • Removed now-unused isInterpolatedTemplateLiteral helper (its logic is subsumed by getStaticStringValue).
  • Tests — corrected the existing console.log(foo) case (was wrongly expecting a suggestion); added a new block covering identifier, object literal, numeric identifier, and direct numeric literal — all report-only.
console.log("ready")      // → reported + core.info("ready") suggestion ✓
console.log(`hello`)      // → reported + core.info(`hello`) suggestion ✓
console.log(user)         // → reported, no suggestion ✓
console.log({ id: 1 })    // → reported, no suggestion ✓
console.log(42)           // → reported, no suggestion ✓

Copilot AI and others added 2 commits July 21, 2026 08:09
…t for static strings

Only attach the `replaceWithCoreMethod` suggestion when the single argument
is statically known to be a string: a string Literal or a TemplateLiteral
with no expressions. Identifiers, object literals, numeric literals, and
other non-string nodes are now reported without a suggestion, because
`core.info` coerces non-strings via string coercion ("[object Object]")
while `console.log` uses `util.inspect`, degrading diagnostic output.

Also remove the now-unused `isInterpolatedTemplateLiteral` helper (the
new logic re-uses `getStaticStringValue` which already handles the
no-expression-template case).

Tests updated: the existing identifier test case (`console.log(foo)`)
is corrected to expect no suggestion, and a new test block asserts
report-only behaviour for identifiers, object literals, and numeric
literals.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unsafe autofix for prefer-core-logging rule fix(eslint-factory): prefer-core-logging suggestion unsafe for non-string arguments Jul 21, 2026
Copilot AI requested a review from pelikhan July 21, 2026 08:11
@pelikhan
pelikhan marked this pull request as ready for review July 21, 2026 09:20
Copilot AI review requested due to automatic review settings July 21, 2026 09:20
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 21, 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 (default_business_additions=0).

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

Restricts prefer-core-logging suggestions to statically known strings, preventing unsafe non-string replacements.

Changes:

  • Gates suggestions using getStaticStringValue.
  • Removes the redundant template-literal helper.
  • Adds regression coverage for non-string arguments.
Show a summary per file
File Description
eslint-factory/src/rules/prefer-core-logging.ts Limits replacement suggestions to static strings.
eslint-factory/src/rules/prefer-core-logging.test.ts Verifies non-string calls remain report-only.

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: 0
  • Review effort level: Medium

@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.

The fix correctly restricts the replaceWithCoreMethod suggestion to statically-known string arguments by gating on getStaticStringValue(arg) !== null. The removed isInterpolatedTemplateLiteral helper is cleanly superseded. Tests cover all key cases (identifier, object literal, numeric literal, direct number). No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 15.1 AIC · ⌖ 4.34 AIC · ⊞ 4.9K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 88/100 — Excellent

Analyzed 13 test cases: 11 design-driven, 2 implementation-detail (edge cases), 0 violations.

📊 Metrics (13 tests)
Metric Value
Analyzed 13 (TypeScript/Vitest)
✅ Design 11 (85%)
⚠️ Implementation 2 (15%)
Edge/error coverage 9 (69%)
Duplicate clusters 0
Inflation ratio 5.9:1 (exceeds 2:1, but appropriate for ESLint rules)
🚨 Violations 0
📋 Test Breakdown

| Test Case | Classification | Coverage |
|---|---|
| uses the correct docs URL | Implementation (metadata) | Smoke test |
| hasSuggestions enabled | Implementation (metadata) | Smoke test |
| plain console.log with no core in scope is now flagged | Design | Safety invariant: no unsafe suggestions for non-string args |
| core.info calls are fine | Design | Valid-code detection |
| console.log in a function where core is not declared | Design | Scope detection |
| console.log when core is in scope via require | Design | Module scope tracking |
| console.error is not flagged | Design | Stream-semantics safety |
| console.warn is not flagged | Design | Stream-semantics safety |
| console.debug when core is in scope | Design | Method-specific behavior |
| console.log when core is a function parameter | Design | Parameter scope tracking |
| console.log with multi-arg call when core is in scope | Design | Multi-arg handling |
| console.log with format specifier is report-only | Design | Format specifier detection |
| NEW: console.log with non-string single argument is report-only | Design | Safety invariant: autofix only for static strings |

🔍 Edge Cases & Assertions

String arguments (autofix enabled):

  • ✅ String literals → suggests core.info(...)
  • ✅ Template literals (no expressions) → suggests replacement

Non-string arguments (report-only, no autofix):

  • ❌ Identifiers with unknown type (const user = {}; console.log(user)) → reports but no suggestion
  • ❌ Object literals (console.log({ id: 1 })) → reports but no suggestion
  • ❌ Numeric variables (const count = 5; console.log(count)) → reports but no suggestion
  • ❌ Numeric literals (console.log(42)) → reports but no suggestion

Safe exemptions:

  • console.error / console.warn correctly exempted (stream semantics)
  • ✅ Multi-argument calls report-only (no suggestion)
  • ✅ Format specifiers (%s, %d) report-only (no suggestion)

Test modifications in this PR:

  • ✅ Line 32: Updated const foo = "bar"; console.log(foo) test to correctly expect no suggestion (non-string identifier at runtime)
  • ✅ Lines 221–271: Added comprehensive 4-case test block for non-string arguments

Verdict

PASS. 85% design-driven tests (threshold: ≤30% implementation).

Strengths:

  • New test block directly addresses the safety invariant: autofix suggestions only for statically-known strings
  • All 4 non-string argument types covered (identifier, object literal, numeric variable, numeric literal)
  • Existing test corrected to reflect new behavior
  • No mock violations or build-tag issues
  • High regression-detection value for the specific bug being fixed

Rationale for 88/100:

  • 85% design tests (11/13) covers the behavioral contract well (+40)
  • 69% edge/error coverage (9/13 have error/edge assertions) (+30)
  • No duplicates (+20)
  • Inflation ratio 5.9:1 slightly exceeds 2:1 threshold (-8 points, but appropriate for ESLint rules)
  • No violations: +6 (rounded)
  • Calculation: (0.85 × 40) + (0.69 × 30) + 20 + 0 + 6 = 88

🧪 Test quality analysis by Test Quality Sentinel · 22.7 AIC · ⌖ 7.52 AIC · ⊞ 7K ·
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.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — approving with one minor cohesion note.

📋 Key Themes & Highlights

Key Themes

  • Root cause properly addressed: gates the suggestion on getStaticStringValue(arg) !== null, fixing the semantic mismatch between console.log (util.inspect) and core.info (string coercion).
  • Dead code removed: isInterpolatedTemplateLiteral is cleanly removed; its logic is already subsumed by getStaticStringValue.
  • Existing test corrected: console.log(foo) was wrongly expecting a suggestion — now correctly report-only.
  • New test coverage: four new cases cover identifier, object literal, numeric identifier, and numeric literal — all report-only.

Positive Highlights

  • ✅ Fix is minimal and surgical — no unrelated changes.
  • ✅ Comment in canSuggestCoreReplacement clearly explains the coercion asymmetry.
  • ✅ Test coverage is thorough for the changed behaviour.

Minor Note

One inline comment on test structure cohesion — fully optional, does not block merge.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 32.5 AIC · ⌖ 4.52 AIC · ⊞ 6.6K
Comment /matt to run again

});
});

it("invalid: console.log with non-string single argument is report-only (no autofix suggestion)", () => {

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] The new non-string test cases use a standalone it/ruleTester.run block, while existing invalid cases share a common block. Coverage is correct — this is a minor cohesion note only.\n\n

\n💡 Optional refactor\n\nConsider merging the four new cases into the existing invalid: block that already owns the single-identifier story (line 32). That keeps all "argument type" cases together and makes the test file easier to scan at a glance.\n\nFully optional — the current layout is readable.\n\n
\n\n@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept this as-is. I considered merging those cases into the earlier block, but kept the dedicated non-string regression block to make the safety boundary explicit in one place.

@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: 88/100 APPROVED.

85% design-driven tests (threshold: ≤30% implementation). New test block directly addresses the safety invariant: autofix suggestions only for statically-known strings. All 4 non-string argument types covered (identifier, object literal, numeric variable, numeric literal). No violations.

@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.

The fix is correct — gating suggestions on getStaticStringValue properly prevents silent output degradation when console.log(obj) would be replaced with core.info(obj). Two non-blocking issues worth fixing before merge:

### Findings
  1. Redundant parse (prefer-core-logging.ts line 86) — getStaticStringValue is called twice for the same node on the valid-string path (once in canSuggestCoreReplacement, then again inside hasConsoleFormatSpecifier). Refactoring hasConsoleFormatSpecifier to accept a string and passing the already-computed value in eliminates the redundancy.

  2. Missing positive test coverage (prefer-core-logging.test.ts line 37) — the PR removes the only test that verified a string-literal argument produces a correct suggestion with the right output text. A regression that breaks the entire suggestion path would now go undetected. A single console.log("hello")core.info("hello") test case with full suggestion output would restore this coverage.

🔎 Code quality review by PR Code Quality Reviewer · 38.1 AIC · ⌖ 4.71 AIC · ⊞ 5.6K
Comment /review to run again

return false;
}

return !hasConsoleFormatSpecifier(arg);

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.

getStaticStringValue is called twice for the same node on every valid-string path.

💡 Details

canSuggestCoreReplacement (line 82) calls getStaticStringValue(arg) to gate the suggestion, then falls through to hasConsoleFormatSpecifier(arg) (line 86), which calls getStaticStringValue(arg) again internally (line 58). The value is parsed twice on every string-argument path.

Simpler and cheaper — pass the already-computed value forward:

function hasConsoleFormatSpecifier(value: string): boolean {
  for (let i = 0; i < value.length - 1; i++) { ... }
  return false;
}

function canSuggestCoreReplacement(node: TSESTree.CallExpression): boolean {
  const arg = node.arguments[0];
  if (node.arguments.length !== 1 || !arg) return false;
  const value = getStaticStringValue(arg);
  if (value === null) return false;
  return !hasConsoleFormatSpecifier(value);
}

This removes the redundant parse and makes the data flow obvious.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 813b76d. canSuggestCoreReplacement now computes getStaticStringValue(arg) once and passes the string value to hasConsoleFormatSpecifier, removing the duplicate parse.

messageId: "preferCoreLogging",
data: { method: "log", replacement: "core.info" },
suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: `foo` }, output: `const foo = "bar"; core.info(foo);` }],
suggestions: [],

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.

No test covers the autofix happy path — the new suggestions: [] change removes the only positive coverage for the suggestion output.

💡 Details

The original test at line 37 had:

suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: `foo` }, output: `const foo = "bar"; core.info(foo);` }]

This was the sole test verifying that a string-literal argument (foo) produces a working autofix with correct output text. Replacing it with suggestions: [] is correct for the fix (the argument foo is an identifier, not a string literal), but it leaves zero tests for the suggestion-present path.

Add a case that exercises a string literal:

{
  code: `console.log("hello");`,
  errors: [
    {
      messageId: "preferCoreLogging",
      data: { method: "log", replacement: "core.info" },
      suggestions: [{
        messageId: "replaceWithCoreMethod",
        data: { replacement: "core.info", args: `"hello"` },
        output: `core.info("hello");`,
      }],
    },
  ],
},

Without this, a future regression that breaks suggestion generation entirely would not be caught by tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already covered: the existing invalid case for console.log("hello") asserts the replaceWithCoreMethod suggestion and expected autofix output. No additional change needed.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

…on gate

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

Copilot AI commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Ran pr-finisher pass. Addressed all in-scope bot review comments, pushed fix commit 813b76d, and validated locally with make fmt, make lint, make test-unit, and make test.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, update the branch if needed, and clear the remaining inline review feedback before rerunning checks:

  • Inline review comment asks to keep the positive autofix happy-path test coverage.
  • Inline review comment was already addressed in commit 813b76d; make sure the review thread is resolved after confirming the change.

Run: https://github.com/github/gh-aw/actions/runs/29826418446

Warning

Firewall blocked 1 domain

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

  • pi.dev

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

network:
  allowed:
    - defaults
    - "pi.dev"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · gpt54 13.2 AIC · ⌖ 6.32 AIC · ⊞ 7K ·
Comment /souschef to run again

@pelikhan
pelikhan merged commit 3924dec into main Jul 21, 2026
1 check failed
@pelikhan
pelikhan deleted the copilot/eslint-factory-fix-autofix-suggestion branch July 21, 2026 11:49
Copilot stopped work on behalf of gh-aw-bot due to an error July 21, 2026 11:49
Copilot AI requested a review from gh-aw-bot July 21, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eslint-factory: prefer-core-logging offers an unsafe autofix for non-string console.* arguments

4 participants