Skip to content

fix(eslint-plugin): [no-shadow] correct rule to match ESLint v10 handling#12182

Merged
JoshuaKGoldberg merged 3 commits into
typescript-eslint:mainfrom
nevette-bailey:nevetteIssue12070
May 27, 2026
Merged

fix(eslint-plugin): [no-shadow] correct rule to match ESLint v10 handling#12182
JoshuaKGoldberg merged 3 commits into
typescript-eslint:mainfrom
nevette-bailey:nevetteIssue12070

Conversation

@nevette-bailey

@nevette-bailey nevette-bailey commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

Overview

The no-shadow rule reports when a variable in an inner scope "shadows" (reuses the same name and hides) a variable in an outer scope. There is a deliberate exception when named function/class expressions are assigned directly to a variable of the same name that is allowed, common, and a harmless self-reference:

var a = function a() {};

In this case, the function's inner name only exists inside of the function itself, so reporting it as a shadow would be noisy and unhelpful.

The Bug

The isOnInitializer helper previously used a range-containment check to decide if this exception should be applied, by asking "is the inner variable located somewhere inside the declaration of the outer variable?" This definition was too loose because it suppressed shadow reports for named expressions anywhere inside an initializer, not just direct initializers, such as this case that should be reported because the inner definition is a genuinely separate binding that shadows the outer one:

const FooBarComponent = memo(function FooBarComponent() {});

ESLint v10 started to correctly report these cases.

The Fix

Instead of asking "is the inner name somewhere inside the initializer?", this PR rewrites to ask "is the inner expression literally the initializer?" It does this by:

  1. Rewriting isOnInitializer to walk from the outer variable to its actual initializer node
  2. Checking whether the inner function/class expression is that node through an identity check and not just range-containment

The unwrapExpression helper handles these still-valid cases where the function expression can transparently become a at runtime:

var a = foo || function a() {};
var a = foo ? function a() {} : bar;

The previous code also had a check for class expressions that verified the node type but not the definition type, even though a definition bundles both. The fix adds a missing DefinitionType.ClassName check for class expressions so that both innerDef.node.type and innerDef.type are now checked.

Fixes #12070.

💖

@typescript-eslint

Copy link
Copy Markdown
Contributor

Thanks for the PR, @nevette-bailey!

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 Mar 31, 2026

Copy link
Copy Markdown

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit 3a69108
🔍 Latest deploy log https://app.netlify.com/projects/typescript-eslint/deploys/69cb35866e9d7000084e7f16
😎 Deploy Preview https://deploy-preview-12182--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: 99 (no change 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

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

@nx-cloud

nx-cloud Bot commented Mar 31, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 3a69108

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

☁️ Nx Cloud last updated this comment at 2026-03-31 02:51:19 UTC

@codecov

codecov Bot commented Mar 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.75%. Comparing base (1bf86c9) to head (3a69108).
⚠️ Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12182      +/-   ##
==========================================
+ Coverage   86.52%   86.75%   +0.22%     
==========================================
  Files         501      512      +11     
  Lines       16275    16306      +31     
  Branches     5069     5079      +10     
==========================================
+ Hits        14082    14146      +64     
+ Misses       1506     1473      -33     
  Partials      687      687              
Flag Coverage Δ
unittest 86.75% <100.00%> (+0.22%) ⬆️

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-shadow.ts 82.58% <100.00%> (+2.58%) ⬆️

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

@nevette-bailey nevette-bailey marked this pull request as draft April 15, 2026 15:59
@nevette-bailey nevette-bailey marked this pull request as ready for review April 15, 2026 15:59

@JoshuaKGoldberg JoshuaKGoldberg left a comment

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.

Super, thanks for this! 🙌

@JoshuaKGoldberg JoshuaKGoldberg changed the title fix(eslint-plugin): correct no-shadow rule to match ESLint v10 handling fix(eslint-plugin): [no-shadow] correct rule to match ESLint v10 handling May 27, 2026
@JoshuaKGoldberg JoshuaKGoldberg merged commit f525814 into typescript-eslint:main May 27, 2026
61 of 63 checks passed
@StyleShit StyleShit mentioned this pull request May 27, 2026
3 tasks
renovate Bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Jun 7, 2026
| datasource | package                          | from   | to     |
| ---------- | -------------------------------- | ------ | ------ |
| npm        | @typescript-eslint/eslint-plugin | 8.60.0 | 8.60.1 |
| npm        | @typescript-eslint/parser        | 8.60.0 | 8.60.1 |


## [v8.60.1](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8601-2026-06-01)

##### 🩹 Fixes

- **eslint-plugin:** \[no-shadow] correct rule to match ESLint v10 handling ([#12182](typescript-eslint/typescript-eslint#12182))
- **eslint-plugin:** respect ECMAScript line terminators in ts-comment rules ([#12352](typescript-eslint/typescript-eslint#12352))

##### ❤️ Thank You

- lumir
- Nevette Bailey [@nevette-bailey](https://github.com/nevette-bailey)

See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.1) 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.
@jraoult

jraoult commented Jun 7, 2026

Copy link
Copy Markdown

@JoshuaKGoldberg @nevette-bailey Hi, assuming we liked the previous behaviour (const FooBarComponent = memo(function FooBarComponent() {}); considered valid), can we restore it? Is there an option for that?

@bradzacher

Copy link
Copy Markdown
Member

https://typescript-eslint.io/contributing/issues#commenting

Do not comment on closed PRs.

@typescript-eslint typescript-eslint locked and limited conversation to collaborators Jun 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: [no-shadow] Difference in handling from ESLint v10 no-shadow rule

4 participants