Skip to content

fix(eslint-plugin): [consistent-indexed-object-style] do not remove comments when fixing#12396

Open
sarathfrancis90 wants to merge 2 commits into
typescript-eslint:mainfrom
sarathfrancis90:fix/consistent-indexed-object-style-preserve-comments
Open

fix(eslint-plugin): [consistent-indexed-object-style] do not remove comments when fixing#12396
sarathfrancis90 wants to merge 2 commits into
typescript-eslint:mainfrom
sarathfrancis90:fix/consistent-indexed-object-style-preserve-comments

Conversation

@sarathfrancis90

Copy link
Copy Markdown

PR Checklist

Overview

The consistent-indexed-object-style fixers build their replacement text by reading the text of a few specific sub-nodes (the key/value type annotations, or a mapped type's constraint and value) and stitching them into a Record<...> / index-signature string. Comments that sit anywhere else within the node being replaced are never copied over, so running the fix silently deletes them. In the mapped-type case from #10577 it can even produce syntactically broken output, because a trailing line comment ends up inside the generated Record<...> arguments.

This adds a small hasComments guard (using getCommentsInside) and uses it at each of the three fix sites. When the reported node contains any comments, the rule still reports the violation but withholds the fix and the index-signature suggestion, rather than emitting output that loses code. This follows the rule's existing pattern of skipping the fix whenever it can't safely produce equivalent output (e.g. the existing Mutable<T> and unsafe-fix cases).

Tests cover record mode (interface, type literal, and mapped type), index-signature mode, and the exact reproduction from the issue; each asserts that no fix or suggestion is applied.

…omments when fixing

The autofix rebuilds the type from the text of a couple of sub-nodes (the
key/value types or the mapped type constraint/value). Any comments that sat
between those sub-nodes were silently dropped, so applying the fix could delete
code.

Withhold the fix (and the index-signature suggestion) whenever the node being
replaced contains comments, mirroring how the rule already skips fixing when it
cannot safely produce equivalent output.

Fixes typescript-eslint#10577
@typescript-eslint

Copy link
Copy Markdown
Contributor

Thanks for the PR, @sarathfrancis90!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

@netlify

netlify Bot commented Jun 2, 2026

Copy link
Copy Markdown

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit ce6eaa9
🔍 Latest deploy log https://app.netlify.com/projects/typescript-eslint/deploys/6a202586740cb0000842bb50
😎 Deploy Preview https://deploy-preview-12396--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 98 (🔴 down 1 from production)
Accessibility: 97 (no change from production)
Best Practices: 100 (no change from production)
SEO: 90 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Jun 2, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit ce6eaa9

Command Status Duration Result
nx run-many -t typecheck:tsgo ✅ Succeeded 35s View ↗
nx test eslint-plugin-internal --coverage=false ✅ Succeeded 4s View ↗
nx run types:build ✅ Succeeded 1s View ↗
nx run integration-tests:test ✅ Succeeded 4s View ↗
nx run-many -t lint --projects=eslint-plugin --... ✅ Succeeded 53s View ↗
nx run-many -t typecheck ✅ Succeeded 47s View ↗
nx run-many -t lint --projects=typescript-estre... ✅ Succeeded 53s View ↗
nx run-many -t lint --projects=parser,type-util... ✅ Succeeded 28s View ↗
Additional runs (38) ✅ Succeeded ... View ↗

☁️ Nx Cloud last updated this comment at 2026-06-03 13:05:59 UTC

@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.98%. Comparing base (4f84a69) to head (ce6eaa9).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12396      +/-   ##
==========================================
- Coverage   86.99%   86.98%   -0.02%     
==========================================
  Files         513      513              
  Lines       16546    16553       +7     
  Branches     5165     5169       +4     
==========================================
+ Hits        14394    14398       +4     
- Misses       1461     1464       +3     
  Partials      691      691              
Flag Coverage Δ
unittest 86.98% <100.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...lugin/src/rules/consistent-indexed-object-style.ts 97.70% <100.00%> (+0.07%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +48 to +51
// The fixers rebuild the type from the text of a few sub-nodes, which would
// silently drop any comments that sit between those sub-nodes. To avoid
// deleting code, we withhold the fix when the node to be replaced contains
// comments.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comment does not describe the function, but rather the PR overall... let's just remove it?

Suggested change
// The fixers rebuild the type from the text of a few sub-nodes, which would
// silently drop any comments that sit between those sub-nodes. To avoid
// deleting code, we withhold the fix when the node to be replaced contains
// comments.

// silently drop any comments that sit between those sub-nodes. To avoid
// deleting code, we withhold the fix when the node to be replaced contains
// comments.
function hasComments(node: TSESTree.Node): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this is the right condition we want to use for when to disable the autofix, because many cases are perfectly fine. For example:

type Foo = {
  [key: string]: {
    /** important comment */
    property: number
  }
};

is perfectly safe to autofix to

type Foo = Record<string, {
    /** important comment */
    property: number
  }>;

, which the rule happily did before this change, but no longer does. Can you find a way to refine the comment detection to allow these unproblematic comments?

}
: null,
fix:
safeFix && !hasComments(node)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

removing the fixer completely is overkill (here and below). let's instead turn the autofix into a suggestion please.

@kirkwaiblinger kirkwaiblinger added the awaiting response Issues waiting for a reply from the OP or another party label Jun 3, 2026
@kirkwaiblinger

kirkwaiblinger commented Jun 3, 2026

Copy link
Copy Markdown
Member

FYI - this PR appears borderline at best as far as complying with the https://typescript-eslint.io/contributing/ai-policy/. Please give it a read to ensure this PR can continue to be reviewed. Thanks!

@sarathfrancis90

Copy link
Copy Markdown
Author

Thanks @kirkwaiblinger — and fair on the policy; I've read it and will keep what I work on to changes I understand and can stand behind, with descriptions in my own words.

On the substance you're right, and I'd over-corrected. The fixer rebuilds the type from the key-type and value-type sub-nodes' text, so any comment that lives inside those sub-nodes (like the /** important comment */ on a nested property in your example) is carried along fine — the only comments actually at risk are ones attached to the index signature itself or sitting between the key and the value. So the guard should key off those specifically, not "any comment on the node".

And agreed that dropping the fixer entirely is too much. I'll narrow the check to the genuinely-lossy case and, for that case, offer the reshape as a suggestion rather than an autofix (so the safe cases keep autofixing and the lossy one becomes opt-in instead of silently dropping comments). I'll also remove that stray comment. Reworking along those lines now.

… instead of dropping the fix on comments

Narrow the comment check so only comments the fixer would actually drop - those
inside the reported node but outside every sub-node whose text the fix reuses -
block the autofix. Comments that live inside a preserved sub-node ride along and
keep autofixing as before.

For the genuinely-lossy case, offer the reshape as a suggestion rather than
withholding it entirely, so converting the type stays available as an explicit
opt-in action instead of silently discarding the comment. The `-readonly`
mapped type stays untouched since there is no builtin Mutable<T> to express it.
@sarathfrancis90

Copy link
Copy Markdown
Author

Reworked along those lines, thanks for the steer:

  • Swapped the broad hasComments guard for hasUnpreservedComments(node, ...preserved), which only flags comments that fall outside the sub-nodes whose text the fixer actually reuses (key/value types, the mapped-type constraint/value, the Record params). Comments nested inside a preserved sub-node now keep autofixing rather than blocking it.
  • For the genuinely-lossy case the reshape is now offered as a suggestion (preferRecordSuggestion, plus the existing preferIndexSignatureSuggestion) instead of being withheld — so converting the type is still available, just as an explicit opt-in rather than something that silently drops the comment. -readonly mapped types stay untouched since there's no builtin Mutable<T>.
  • Removed the stray comment.

Tests updated: the previously-withheld cases now assert the suggestion output (including the #10577 repro). Rule tests, typecheck, lint and prettier all pass locally.

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

Labels

awaiting response Issues waiting for a reply from the OP or another party

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: [consistent-indexed-object-style] autofix removes comments in many places

2 participants