npm package: @rushstack/eslint-plugin
Description: An ESLint plugin providing supplementary rules for use with the @rushstack/eslint-config package
Latest version: 0.23.2

# @rushstack/eslint-plugin

This plugin implements supplementary rules for use with the `@rushstack/eslint-config` package,
which provides a TypeScript ESLint ruleset tailored for large teams and projects.
Please see [that project's documentation](https://www.npmjs.com/package/@rushstack/eslint-config)
for details.  To learn about Rush Stack, please visit: [https://rushstack.io/](https://rushstack.io/)

## `@rushstack/hoist-jest-mock`

Require Jest module mocking APIs to be called before any other statements in their code block.

#### Rule Details

Jest module mocking APIs such as "jest.mock()" must be called before the associated module is imported, otherwise
they will have no effect. Transpilers such as `ts-jest` and `babel-jest` automatically "hoist" these calls, however
this can produce counterintuitive behavior. Instead, the `hoist-jest-mocks` lint rule simply requires developers
to write the statements in the correct order.

The following APIs are affected: 'jest.mock()', 'jest.unmock()', 'jest.enableAutomock()', 'jest.disableAutomock()',
'jest.deepUnmock()'.

For technical background, please read the Jest documentation here: https://jestjs.io/docs/en/es6-class-mocks

#### Examples

The following patterns are considered problems when `@rushstack/hoist-jest-mock` is enabled:

```ts
import * as file from './file'; // import statement
jest.mock('./file'); // error

test("example", () => {
  jest.mock('./file2'); // error
});
```

```ts
require('./file'); // import statement
jest.mock('./file'); // error
```

The following patterns are NOT considered problems:

```ts
jest.mock('./file'); // okay, because mock() precedes the import below
import * as file from './file'; // import statement
```

```ts
// These statements are not real "imports" because they import compile-time types
// without any runtime effects
import type { X } from './file';
let y: typeof import('./file');

jest.mock('./file'); // okay
```

## `@rushstack/import-requires-chunk-name`

Require each dynamic `import()` used for code splitting to specify exactly one Webpack chunk name via a magic comment.

#### Rule Details

When using dynamic `import()` to create separately loaded chunks, Webpack (and compatible bundlers such as Rspack) can assign a deterministic name if a `/* webpackChunkName: 'my-chunk' */` or `// webpackChunkName: "my-chunk"` magic comment is provided. Without an explicit name, the bundler falls back to autogenerated identifiers (often numeric or hashed), which:

- Are less stable across refactors, hurting long‑term caching
- Make bundle analysis and performance troubleshooting harder
- Can produce confusing diffs during code reviews

This rule enforces that:

1. Every `import(<specifier>)` expression used for code splitting includes a chunk name magic comment inside its parentheses.
2. Exactly one chunk name is declared. Multiple chunk name comments (or a single comment containing multiple comma‑separated `webpackChunkName` entries) are flagged.

The chunk name must appear in either a block or line comment inside the `import()` call. Accepted forms:

```ts
import(/* webpackChunkName: 'feature-settings' */ './feature/settings');
import(
  /* webpackChunkName: "feature-settings" */
  './feature/settings'
);
import(
  // webpackChunkName: "feature-settings"
  './feature/settings'
);
```

No options are currently supported.

For background on magic comments, see: https://webpack.js.org/api/module-methods/#magic-comments

#### Examples

The following patterns are considered problems when `@rushstack/import-requires-chunk-name` is enabled:

```ts
// Missing chunk name
import('./feature/settings'); // error
```

```ts
// Multiple chunk name comments
import(
  /* webpackChunkName: 'feature-settings' */
  /* webpackChunkName: 'feature-settings-alt' */
  './feature/settings'
); // error
```

```ts
// Multiple chunk names in a single comment (comma separated)
import(
  /* webpackChunkName: 'feature-settings', webpackChunkName: 'feature-settings-alt' */
  './feature/settings'
); // error
```

The following patterns are NOT considered problems:

```ts
// Single block comment with one chunk name
import(/* webpackChunkName: 'feature-settings' */ './feature/settings');
```

```ts
// Multiline formatting with a block comment
import(
  /* webpackChunkName: 'feature-settings' */
  './feature/settings'
);
```

```ts
// Line comment form
import(
  // webpackChunkName: 'feature-settings'
  './feature/settings'
);
```

#### Notes

- If your bundler does not understand Webpack magic comments (e.g. plain Node ESM loader), disable this rule for that project.
- Choose stable, descriptive chunk names—avoid including hashes, timestamps, or environment‑specific tokens.
- Chunk names share a global namespace in the final bundle; avoid collisions to keep analysis clear.

#### Rationale

Explicit chunk naming improves cache hit rates, observability, and maintainability. Enforcing the practice via an ESLint rule prevents missing or duplicate declarations that could lead to unpredictable bundle naming.

## `@rushstack/no-backslash-imports`

Prevent import and export specifiers from using Windows-style backslashes in module paths.

#### Rule Details

JavaScript module specifiers always use POSIX forward slashes. Using backslashes (e.g. `import './src\utils'`) can lead to inconsistent behavior across tools, and may break resolution in some environments. This rule flags any import or export whose source contains a `\` character and provides an autofix that replaces backslashes with `/`.

#### Examples

The following patterns are considered problems when `@rushstack/no-backslash-imports` is enabled:

```ts
import helper from './lib\\helper'; // error (autofix -> './lib/helper')
export * from './data\\items'; // error
```

The following patterns are NOT considered problems:

```ts
import helper from './lib/helper';
export * from '../data/items';
```

#### Notes

- Works for `import`, dynamic `import()`, and `export ... from` forms.
- Loader/query strings (e.g. `raw-loader!./file`) are preserved during the fix; only path separators are changed.

#### Rationale

Forward slashes are portable and avoid subtle cross-platform inconsistencies. Autofixing reduces churn and enforces a predictable style.

## `@rushstack/no-external-local-imports`

Prevent relative imports that reach outside the configured TypeScript `rootDir` (if specified) or outside the package boundary.

#### Rule Details

Local relative imports should refer only to files that are part of the compiling unit: either under the package directory or (when a `rootDir` is configured) under that root. Reaching outside can accidentally couple a package to sibling projects, untracked build inputs, or files excluded from type checking. This rule resolves each relative import/ export source and ensures the target is contained within the effective root. If not, it is flagged.

#### Examples

Assume `rootDir` is `src` and the package folder is `/repo/packages/example`:

```ts
// In /repo/packages/example/src/components/Button.ts
import '../utils/file'; // error if '../utils/file' is outside src
import '../../../other-package/src/index'; // error (outside package root)
```

```ts
// In /repo/packages/example/src/index.ts
import './utils/file'; // passes (inside rootDir)
```

#### Notes

- Only relative specifiers are checked. Package specifiers (`react`, `lodash`) are ignored.
- If no `rootDir` is defined, the package directory acts as the boundary.
- Useful for enforcing project isolation in monorepos.

#### Rationale

Prevents accidental dependencies on files that aren’t part of the compilation or publishing surface, improving encapsulation and build reproducibility.

## `@rushstack/no-new-null`

Prevent usage of the JavaScript `null` value, while allowing code to access existing APIs that
may require `null`.

#### Rule Details

Most programming languages have a "null" or "nil" value that serves several purposes:

1. the initial value for an uninitialized variable
2. the value of `x.y` or `x["y"]` when `x` has no such key, and
3. a special token that developers can assign to indicate an unknown or empty state.

In JavaScript, the `undefined` value fulfills all three roles.  JavaScript's `null` value is a redundant secondary
token that only fulfills (3), even though its name confusingly implies otherwise.  The `null` value was arguably
a mistake in the original JavaScript language design, but it cannot be banned entirely because it is returned
by some entrenched system APIs such as `JSON.parse()`, and also some popular NPM packages.  Thus, this rule aims
to tolerate preexisting `null` values while preventing new ones from being introduced.

The `@rushstack/no-new-null` rule flags type definitions with `null` that can be exported or used by others.
The rule ignores declarations that are local variables, private members, or types that are not exported.

If you are designing a new JSON file format, it's a good idea to avoid `null` entirely.  In most cases
there are better representations that convey more information about an item that is unknown, omitted,
or disabled.  If you do need to declare types for JSON structures containing `null`, rather than
suppressing the lint rule, you can use a specialized
[JsonNull](https://api.rushstack.io/pages/node-core-library.jsonnull/)
type as provided by [@rushstack/node-core-library](https://www.npmjs.com/package/@rushstack/node-core-library).

#### Examples

The following patterns are considered problems when `@rushstack/no-new-null` is enabled:

```ts
// interface declaration with null field
interface IHello { hello: null; } // error

// type declaration with null field
type Hello = { hello: null; } // error

// type function alias
type T = (args: string | null) => void; // error

// type alias
type N = null; // error

// type constructor
type C = {new (args: string | null)} // error

// function declaration with null args
function hello(world: string | null): void {}; // error
function legacy(callback: (err: Error| null) => void): void { }; // error

// function with null return type
function hello(): (err: Error | null) => void {}; // error

// const with null type
const nullType: 'hello' | null = 'hello'; // error

// classes with publicly visible properties and methods
class PublicNulls {
  property: string | null; // error
  propertyFunc: (val: string | null) => void; // error
  legacyImplicitPublic(hello: string | null): void {} // error
  public legacyExplicitPublic(hello: string | null): void {} // error
}
```

The following patterns are NOT considered problems:

```ts
// wrapping an null-API
export function ok(hello: string): void {
  const innerCallback: (err: Error | null) => void = (e) => {}; // passes
  return innerCallback(null);
}

// classes where null APIs are used, but are private-only
class PrivateNulls {
  private pField: string | null; // passes
  private pFunc: (val: string | null) => void; // passes
  private legacyPrivate(hello: string | null): void { // passes
    this.pField = hello;
    this.pFunc(this.pField)
    this.pFunc('hello')
  }
}
```

## `@rushstack/no-null`

(Deprecated) Prevent usage of JavaScript's `null` keyword.

#### Rule Details

This rule has been superseded by `@rushstack/no-new-null`, and is maintained to support code that has not
migrated to the new rule yet. The `@rushstack/no-null` rule prohibits `null` as a literal value, but allows
it in type annotations.  Comparisons with `null` are also allowed.

#### Examples

The following patterns are considered problems when `@rushstack/no-null` is enabled:

```ts
let x = null;  // error

f(null); // error

function g() {
    return null; // error
}
```

The following patterns are NOT considered problems:

```ts
let x: number | null = f(); // declaring types as possibly "null" is okay

if (x === null) {  // comparisons are okay
    x = 0;
}
```

## `@rushstack/no-transitive-dependency-imports`

Prevent importing modules from transitive dependencies that are not declared in the package’s direct dependency list.

#### Rule Details

Packages should only import modules from their own direct dependencies. Importing a transitive dependency (available only because another dependency pulled it in) creates hidden coupling and can break when versions change. This rule detects any import path containing multiple `node_modules` segments (for relative paths) or any direct reference to a nested `node_modules` folder for package specifiers, flagging such usages.

Allowed exception: a single relative traversal into `node_modules` (e.g. `import '../node_modules/some-pkg/dist/index.js'`) is tolerated to support bypassing package `exports` fields intentionally. Additional traversals are disallowed.

#### Examples

The following patterns are considered problems when `@rushstack/no-transitive-dependency-imports` is enabled:

```ts
// Transitive dependency via deep relative path
import '../../node_modules/some-pkg/node_modules/other-pkg/lib/internal'; // error (multiple node_modules segments)

// Direct package import that resolves into nested node_modules (caught via parsing)
import 'other-pkg/node_modules/inner-pkg'; // error
```

The following patterns are NOT considered problems:

```ts
// Direct dependency
import 'react';

// Single bypass to reach a file export
import '../node_modules/some-pkg/dist/index.js';
```

#### Notes

- Encourages declaring needed dependencies explicitly in `package.json`.
- Reduces breakage due to indirect version changes.

#### Rationale

Explicit declarations keep dependency graphs understandable and maintainable; avoiding transitive imports prevents fragile build outcomes.

## `@rushstack/no-untyped-underscore` (Opt-in)

Prevent TypeScript code from accessing legacy JavaScript members whose name has an underscore prefix.

#### Rule Details

JavaScript does not provide a straightforward way to restrict access to object members, so API names commonly
indicate a private member by using an underscore prefix (e.g. `exampleObject._privateMember`).  For inexperienced
developers who may be unfamiliar with this convention, in TypeScript we can mark the APIs as `private` or omit them
from the typings.  However, when migrating a large code base to TypeScript, it may be difficult to declare types
for every legacy API.  In this situation, the `@rushstack/no-untyped-underscore` rule can help.

This rule detects expressions that access a member with an underscore prefix, EXCEPT in cases where:

- The object is typed: specifically, `exampleObject` has a TypeScript type that declares `_privateMember`; OR
- The object expression uses: the `this` or `super` keywords; OR
- The object expression is a variable named `that`.  (In older ES5 code, `that` was commonly used as an alias
 for `this` in unbound contexts.)

#### Examples

The following patterns are considered problems when `@rushstack/no-untyped-underscore` is enabled:

```ts
let x: any;
x._privateMember = 123;  // error, because x is untyped

let x: { [key: string]: number };
x._privateMember = 123;  // error, because _privateMember is not a declared member of x's type
```

The following patterns are NOT considered problems:

```ts
let x: { _privateMember: any };
x._privateMember = 123;  // okay, because _privateMember is declared by x's type

let x = { _privateMember: 0 };
x._privateMember = 123;  // okay, because _privateMember is part of the inferred type

enum E {
    _PrivateMember
}
let e: E._PrivateMember = E._PrivateMember; // okay, because _PrivateMember is declared by E
```

## `@rushstack/normalized-imports`

Require relative import paths to be written in a normalized minimal form and autofix unnecessary directory traversals.

#### Rule Details

Developers sometimes write relative paths with redundant traversals (e.g. `import '../module'` when already in the parent, or `import '././utils'`). This rule computes the shortest relative path between the importing file and target, rewrites it using POSIX separators, and ensures a leading `./` is present when needed. Non-relative (package) imports are ignored.

If the provided path differs from the normalized form, the rule reports it and autofixes to the canonical specifier while preserving loader/query suffixes.

#### Examples

The following patterns are considered problems when `@rushstack/normalized-imports` is enabled:

```ts
// Redundant parent traversal
import '../currentDir/utils'; // error (autofix -> './utils')

// Repeated ./ segments
import '././components/Button'; // error (autofix -> './components/Button')
```

The following patterns are NOT considered problems:

```ts
import './utils';
import '../shared/types';
```

#### Notes

- Only relative paths (`./` or `../`) are normalized.
- Helps produce deterministic diff noise and cleaner refactors.

#### Rationale

Consistent relative paths improve readability and make large-scale moves/renames less error-prone.

## `@rushstack/pair-react-dom-render-unmount`

Require ReactDOM (legacy) render trees created in a file to be explicitly unmounted in that same file to avoid memory leaks.

#### Rule Details

React 18 introduced `ReactDOM.createRoot()` and `root.unmount()`, but many codebases still use the legacy APIs:

- `ReactDOM.render(element, container)`
- `ReactDOM.unmountComponentAtNode(container)`

If a component tree is rendered and the container node is later discarded without an explicit unmount, detached DOM nodes and event handlers may remain in memory. This rule enforces a simple pairing discipline: the total number of render calls in a file must match the total number of unmount calls. If they differ, every render and unmount in the file is flagged so the developer can reconcile them.

The rule detects both namespace invocations (e.g. `ReactDOM.render(...)`) and separately imported named functions (e.g. `import { render, unmountComponentAtNode } from 'react-dom'`). Default or namespace imports (e.g. `import * as ReactDOM from 'react-dom'` or `import ReactDOM from 'react-dom'`) are supported.

No configuration options are currently supported.

#### Examples

The following patterns are considered problems when `@rushstack/pair-react-dom-render-unmount` is enabled:

```ts
import * as ReactDOM from 'react-dom';
ReactDOM.render(<App /> , document.getElementById('root'));
// Missing matching unmount
```

```ts
import { render } from 'react-dom';
render(<App /> , document.getElementById('root'));
// Missing matching unmountComponentAtNode
```

```ts
import { unmountComponentAtNode } from 'react-dom';
// Unmount without a corresponding render in this file
unmountComponentAtNode(document.getElementById('root')!);
```

```ts
import { render, unmountComponentAtNode } from 'react-dom';
render(<A /> , a);
render(<B /> , b);
// Only one unmount
unmountComponentAtNode(a);
// "b"'s render is not paired
```

The following patterns are NOT considered problems:

```ts
import * as ReactDOM from 'react-dom';
const rootEl = document.getElementById('root');
ReactDOM.render(<App /> , rootEl);
ReactDOM.unmountComponentAtNode(rootEl!);
```

```ts
import { render, unmountComponentAtNode } from 'react-dom';
render(<A /> , a);
render(<B /> , b);
unmountComponentAtNode(a);
unmountComponentAtNode(b);
// All renders paired
```

```ts
// No legacy ReactDOM render/unmount usage in this file
// (e.g. uses React 18 createRoot API or just defines components) — rule passes
```

#### Notes

- The rule does not attempt dataflow analysis to verify the same container node is passed; it only enforces count parity.
- Modern React apps using `createRoot()` should migrate to pairing `root.unmount()`. This legacy rule helps older code until migration is complete.
- Multiple files can coordinate unmounting (e.g. via a shared cleanup utility); in that case this rule will flag the imbalance—consider colocating the unmount or disabling the rule for that file.

#### Rationale

Unpaired legacy renders are a common cause of memory leaks and test pollution. A lightweight count-based heuristic catches most oversights without requiring complex static analysis.

## `@rushstack/typedef-var`

Require explicit type annotations for top-level variable declarations, while exempting local variables within function or method scopes.

#### Rule Details

This rule is implemented to supplement the deprecated `@typescript-eslint/typedef` rule. The `@typescript-eslint/typedef` rule was deprecated based on the judgment that "unnecessary type annotations, where type inference is sufficient, can be cumbersome to maintain and generally reduce code readability."

However, we prioritize code reading and maintenance over code authorship. That is, even when the compiler can infer a type, this rule enforces explicit type annotations to ensure that a code reviewer (e.g., when viewing a GitHub Diff) does not have to rely entirely on inference and can immediately ascertain a variable's type. This approach makes writing code harder but significantly improves the more crucial activity of reading and reviewing code.

Therefore, the `@rushstack/typedef-var` rule enforces type annotations for all variable declarations outside of local function or class method scopes. This includes the module's top-level scope and any block scopes that do not belong to a function or method.

To balance this strictness with code authoring convenience, the rule deliberately relaxes the type annotation requirement for the following local variable declarations:

- Variable declarations within a function body.
- Variable declarations within a class method.
- Variables declared via object or array destructuring assignments.

#### Examples

The following patterns are considered problems when `@rushstack/typedef-var` is enabled:

```ts
// Top-level declarations lack explicit type annotations
const x = 123; // error

let x = 123; // error

var x = 123; // error
```

```ts
// Declaration within a non-function block scope
{
  const x = 123; // error
}
```

The following patterns are NOT considered problems:

```ts
// Local variables inside function expressions are exempt
function f() { const x = 123; } // passes

const f = () => { const x = 123; }; // passes

const f = function() { const x = 123; } // passes
```

```ts
// Local variables inside class methods are exempt
class C {
  public m(): void {
    const x = 123; // passes
  }
}

class C {
  public m = (): void => {
    const x = 123; // passes
  }
}
```

```ts
// Array and Object Destructuring assignments are exempt
let { a, b } = { // passes
  a: 123,
  b: 234
}
```

## Links

- [CHANGELOG.md](
  https://github.com/microsoft/rushstack/blob/main/eslint/eslint-plugin/CHANGELOG.md) - Find
  out what's new in the latest version

`@rushstack/eslint-plugin` is part of the [Rush Stack](https://rushstack.io/) family of projects.