Skip to content

fix(eslint-plugin): [no-unused-vars] remove trailing newline when removing entire import#11990

Merged
JoshuaKGoldberg merged 1 commit intotypescript-eslint:mainfrom
MariaSolOs:empty-line-removal
Jan 31, 2026
Merged

fix(eslint-plugin): [no-unused-vars] remove trailing newline when removing entire import#11990
JoshuaKGoldberg merged 1 commit intotypescript-eslint:mainfrom
MariaSolOs:empty-line-removal

Conversation

@MariaSolOs
Copy link
Copy Markdown
Contributor

@MariaSolOs MariaSolOs commented Jan 21, 2026

PR Checklist

Overview

The fixer introduced in #11922 leaves blank lines when removing the entire import statement. This PR modifies the fixer to remove trailing new lines when the entire statement is unused.

@typescript-eslint
Copy link
Copy Markdown
Contributor

Thanks for the PR, @MariaSolOs!

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
Copy link
Copy Markdown

netlify bot commented Jan 21, 2026

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit 2615be3
🔍 Latest deploy log https://app.netlify.com/projects/typescript-eslint/deploys/697a859bf1a6d500085b2b46
😎 Deploy Preview https://deploy-preview-11990--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: 72 (🔴 down 21 from production)
Accessibility: 97 (no change from production)
Best Practices: 100 (no change from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

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

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Jan 21, 2026

View your CI Pipeline Execution ↗ for commit 2615be3

Command Status Duration Result
nx run-many -t lint ✅ Succeeded 3m 19s View ↗
nx run-many -t typecheck ✅ Succeeded 1m 59s View ↗
nx test eslint-plugin-internal --coverage=false ✅ Succeeded 4s View ↗
nx run integration-tests:test ✅ Succeeded 6s View ↗
nx test typescript-estree --coverage=false ✅ Succeeded 2s View ↗
nx run types:build ✅ Succeeded 2s View ↗
nx run generate-configs ✅ Succeeded 7s View ↗
nx run-many --target=build --parallel --exclude... ✅ Succeeded 4s View ↗
Additional runs (28) ✅ Succeeded ... View ↗

☁️ Nx Cloud last updated this comment at 2026-01-28 22:02:13 UTC

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.80%. Comparing base (d50aa18) to head (2615be3).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11990      +/-   ##
==========================================
+ Coverage   90.78%   90.80%   +0.01%     
==========================================
  Files         530      531       +1     
  Lines       54402    54637     +235     
  Branches     9196     9228      +32     
==========================================
+ Hits        49391    49611     +220     
- Misses       4998     5015      +17     
+ Partials       13       11       -2     
Flag Coverage Δ
unittest 90.80% <100.00%> (+0.01%) ⬆️

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

Files with missing lines Coverage Δ
packages/eslint-plugin/src/rules/no-unused-vars.ts 97.92% <100.00%> (+0.05%) ⬆️

... and 19 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.

@JoshuaKGoldberg
Copy link
Copy Markdown
Member

leaves blank lines when removing the entire import statement

As much as I dislike leaving blank lines around, I don't think this is in the scope of what we'd want to add a lot of code to handle? Detection/practices around blank lines are not in the purview of the rule. The general project stance is that if folks care about that, they should use a separate tool: preferably (to us) a dedicated formatter, or alternately a dedicated lint rule.

If this PR was just a couple lines it'd be a lot more palatable. But at >30 lines, although I appreciate the work that went into it, I don't think we'd want to take on that maintenance burden.

@MariaSolOs
Copy link
Copy Markdown
Contributor Author

@JoshuaKGoldberg I understand. I do want to mention that before this fixer was added I was using eslint-plugin-unused-imports, which does handle this edge case with blank lines. I do prefer the built-in fixer, but this feature is something that I miss from the other plugin.

they should use a separate tool: preferably (to us) a dedicated formatter, or alternately a dedicated lint rule.

I agree that whitespace should preferably be always handled with formatters, but in this specific case a tool like Prettier won't remove the new blank line (it just enforces that there are no multiple lines between imports). Moreover lint rules like import/order can be configured to prohibit blank lines between import groups, which conflict with the modifications of this fixer.

Detection/practices around blank lines are not in the purview of the rule.

I again agree with you here, but intuitively I think that the least "disruptive" modification when removing an import statement is to remove the new line.

@kirkwaiblinger
Copy link
Copy Markdown
Member

@MariaSolOs this would require an issue first in order to be considered. It's a nontrivial PR for an ordinary feature that doesn't fall under the "minor documentation changes" exception; see https://typescript-eslint.io/contributing/pull-requests

Marking as draft for now, pending issue acceptance.

@MariaSolOs
Copy link
Copy Markdown
Contributor Author

@kirkwaiblinger fair enough, my bad for not following the right order here. Issue created: #11996

Comment thread packages/eslint-plugin/src/rules/no-unused-vars.ts
@MariaSolOs MariaSolOs marked this pull request as ready for review January 23, 2026 20:35
Comment thread packages/eslint-plugin/src/rules/no-unused-vars.ts
Copy link
Copy Markdown
Member

@kirkwaiblinger kirkwaiblinger left a comment

Choose a reason for hiding this comment

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

This version is slightly buggy and again has explicit references to yucky things like \r\n. I would nudge you to take another look at using .trim() as suggested in https://github.com/typescript-eslint/typescript-eslint/pull/11990/changes#r2718706741

@MariaSolOs
Copy link
Copy Markdown
Contributor Author

MariaSolOs commented Jan 26, 2026

Hmm not sure why the CI checks were cancelled...

Copy link
Copy Markdown
Member

@bradzacher bradzacher left a comment

Choose a reason for hiding this comment

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

This approach should work.

An alternate approach might be to use tokens:

  1. get the token (incl comments) before the import decl
    1. if it's on the same line then the decl is not on its own line
    2. else it's on its own line and and set delete start to prevToken.range[1]
  2. get the token (incl comments) after the import decl
    1. if it's on the same line then the decl is not on its own line
    2. else it's on its own line and and set delete start to nextToken.range[0]

That would let us guarantee that we can delete extra whitespace (which is a nice benefit).

There are two edge cases to handle with this though:

  1. if the import decl is the first statement in the file
  2. if the import decl is the last statement before non-decls

IDK if this is better than your current approach.. probably not due to the edge cases?


1 extra test and I think we're probably good to go?

@MariaSolOs
Copy link
Copy Markdown
Contributor Author

This approach should work.

An alternate approach might be to use tokens:

  1. get the token (incl comments) before the import decl

    1. if it's on the same line then the decl is not on its own line
    2. else it's on its own line and and set delete start to prevToken.range[1]
  2. get the token (incl comments) after the import decl

    1. if it's on the same line then the decl is not on its own line
    2. else it's on its own line and and set delete start to nextToken.range[0]

That would let us guarantee that we can delete extra whitespace (which is a nice benefit).

There are two edge cases to handle with this though:

  1. if the import decl is the first statement in the file
  2. if the import decl is the last statement before non-decls

IDK if this is better than your current approach.. probably not due to the edge cases?

1 extra test and I think we're probably good to go?

Yeah because of edge cases I think this is as pretty as it gets.

Copy link
Copy Markdown
Member

@kirkwaiblinger kirkwaiblinger left a comment

Choose a reason for hiding this comment

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

I'm happy with this as-is. The token approach sounds fine to me too so feel free to go with that but it looks like it'll just be a bit longer since you have to do the same wrangling of the edge case when there's no next line 🤷‍♂️

Thanks for sending this PR!

@kirkwaiblinger kirkwaiblinger added the 1 approval >=1 team member has approved this PR; we're now leaving it open for more reviews before we merge label Jan 30, 2026
@MariaSolOs
Copy link
Copy Markdown
Contributor Author

I'm happy with this as-is. The token approach sounds fine to me too so feel free to go with that but it looks like it'll just be a bit longer since you have to do the same wrangling of the edge case when there's no next line 🤷‍♂️

Thanks for sending this PR!

Thanks to you for all the feedback! Is always a pleasure to contribute to this project :)

Copy link
Copy Markdown
Member

@JoshuaKGoldberg JoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

Ok I have to admit, it is nice to see all the removed lines in the test file 😄

Great work, thanks @MariaSolOs! Both on advocating for the change and implementing (+ re-implementing) it.

@JoshuaKGoldberg JoshuaKGoldberg merged commit 3df0002 into typescript-eslint:main Jan 31, 2026
69 of 70 checks passed
@MariaSolOs MariaSolOs deleted the empty-line-removal branch January 31, 2026 20:54
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Feb 10, 2026
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.54.0 | 8.55.0 |
| npm        | @typescript-eslint/parser        | 8.54.0 | 8.55.0 |


## [v8.55.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8550-2026-02-09)

##### 🚀 Features

- **utils:** deprecate defaultOptions in favor of meta.defaultOptions ([#11992](typescript-eslint/typescript-eslint#11992))

##### 🩹 Fixes

- **eslint-plugin:** \[no-useless-default-assignment] reduce param index to ts this handling ([#11949](typescript-eslint/typescript-eslint#11949))
- **eslint-plugin:** \[no-useless-default-assignment] report unnecessary defaults in ternary expressions ([#11984](typescript-eslint/typescript-eslint#11984))
- **eslint-plugin:** \[no-useless-default-assignment] require strictNullChecks ([#11966](typescript-eslint/typescript-eslint#11966), [#12000](typescript-eslint/typescript-eslint#12000))
- **eslint-plugin:** \[no-unused-vars] remove trailing newline when removing entire import ([#11990](typescript-eslint/typescript-eslint#11990))

##### ❤️ Thank You

- Christian Rose [@chrros95](https://github.com/chrros95)
- Josh Goldberg
- Maria Solano [@MariaSolOs](https://github.com/MariaSolOs)
- Minyeong Kim [@minyeong981](https://github.com/minyeong981)
- SungHyun627 [@SungHyun627](https://github.com/SungHyun627)
- Yukihiro Hasegawa [@y-hsgw](https://github.com/y-hsgw)

See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.55.0) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Feb 10, 2026
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.54.0 | 8.55.0 |
| npm        | @typescript-eslint/parser        | 8.54.0 | 8.55.0 |


## [v8.55.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8550-2026-02-09)

##### 🚀 Features

- **utils:** deprecate defaultOptions in favor of meta.defaultOptions ([#11992](typescript-eslint/typescript-eslint#11992))

##### 🩹 Fixes

- **eslint-plugin:** \[no-useless-default-assignment] reduce param index to ts this handling ([#11949](typescript-eslint/typescript-eslint#11949))
- **eslint-plugin:** \[no-useless-default-assignment] report unnecessary defaults in ternary expressions ([#11984](typescript-eslint/typescript-eslint#11984))
- **eslint-plugin:** \[no-useless-default-assignment] require strictNullChecks ([#11966](typescript-eslint/typescript-eslint#11966), [#12000](typescript-eslint/typescript-eslint#12000))
- **eslint-plugin:** \[no-unused-vars] remove trailing newline when removing entire import ([#11990](typescript-eslint/typescript-eslint#11990))

##### ❤️ Thank You

- Christian Rose [@chrros95](https://github.com/chrros95)
- Josh Goldberg
- Maria Solano [@MariaSolOs](https://github.com/MariaSolOs)
- Minyeong Kim [@minyeong981](https://github.com/minyeong981)
- SungHyun627 [@SungHyun627](https://github.com/SungHyun627)
- Yukihiro Hasegawa [@y-hsgw](https://github.com/y-hsgw)

See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.55.0) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Feb 11, 2026
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.54.0 | 8.55.0 |
| npm        | @typescript-eslint/parser        | 8.54.0 | 8.55.0 |


## [v8.55.0](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8550-2026-02-09)

##### 🚀 Features

- **utils:** deprecate defaultOptions in favor of meta.defaultOptions ([#11992](typescript-eslint/typescript-eslint#11992))

##### 🩹 Fixes

- **eslint-plugin:** \[no-useless-default-assignment] reduce param index to ts this handling ([#11949](typescript-eslint/typescript-eslint#11949))
- **eslint-plugin:** \[no-useless-default-assignment] report unnecessary defaults in ternary expressions ([#11984](typescript-eslint/typescript-eslint#11984))
- **eslint-plugin:** \[no-useless-default-assignment] require strictNullChecks ([#11966](typescript-eslint/typescript-eslint#11966), [#12000](typescript-eslint/typescript-eslint#12000))
- **eslint-plugin:** \[no-unused-vars] remove trailing newline when removing entire import ([#11990](typescript-eslint/typescript-eslint#11990))

##### ❤️ Thank You

- Christian Rose [@chrros95](https://github.com/chrros95)
- Josh Goldberg
- Maria Solano [@MariaSolOs](https://github.com/MariaSolOs)
- Minyeong Kim [@minyeong981](https://github.com/minyeong981)
- SungHyun627 [@SungHyun627](https://github.com/SungHyun627)
- Yukihiro Hasegawa [@y-hsgw](https://github.com/y-hsgw)

See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.55.0) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1 approval >=1 team member has approved this PR; we're now leaving it open for more reviews before we merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: [no-unused-vars] Unused imports fixer creates blank lines when removing entire import statement

4 participants