npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-empty-lines

v1.0.0

Published

A stylistic ESLint spacing rule that limits consecutive empty lines by scope

Readme

A stylistic ESLint plugin designed to keep code delightfully dense or spacious by enforcing sensible limits on consecutive empty lines within functions, classes, JSX, TS, and other syntactic neighborhoods. A merciful alternative to ESLint's no-multiple-empty-lines rule, which slashes LOC metrics and obliterates career trajectories with its scorched-earth approach to vertical spacing.

⊳ Includes every ESLint rule along with a few handy options and helpers Rules tested with ESLint v9 against ecma262/2018 (should work with earlier versions) Untested rule targets are prefixed with an underscore (_<rule>)

▎INSTALL

# pick your poison
npm install --save-dev eslint-plugin-empty-lines
bun  add --dev eslint-plugin-empty-lines
pnpm add --save-dev eslint-plugin-empty-lines
yarn add --dev eslint-plugin-empty-lines

QuickStart

🟢 Pass

/* eslint empty-lines/function: ["warn", {"max":1}] */
const pleaseDontEatMyLoc = (thanosSnap = 0) => {
  const loc = 1 / thanosSnap;

  return loc;
}

🔴 Fail

/* eslint empty-lines/function: ["warn", {"max":1}] */
const pleaseDontEatMyLoc = (thanosSnap = 0) => {
  const loc = 1 / thanosSnap;



  return loc;
}

🔴 Auto Fix 🟢

/* eslint empty-lines/jsx: ["warn", {"max":0,"inline":""}] */
const pleaseEatMyLoc = (howMany = 'Less Than None') => {

  return (<div>

    <h1>How Many Is Too Many?</h1>
    <h2>{howMany}</h2

    >
  </

    div

    >);
};

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const pleaseEatMyLoc = (howMany = 'Less Than None') => {

  return (<div><h1>How Many Is Too Many?</h1>
    <h2>{howMany}</h2>
  </div>);
};

Configuration

Add the plugin and rule to your ESLint (eslint.config.mjs, .eslintrc.js, .eslintrc.json, etc.)

{
  "empty-lines/array":                                  [1, {"max": 1}],
  "empty-lines/array-assignment":                       [1, {"max": 1}],
  "empty-lines/array-expression":                       [1, {"max": 1}],
  "empty-lines/array-pattern":                          [1, {"max": 1}],
  "empty-lines/arrow-function-expression":              [1, {"max": 1}],
  "empty-lines/binary":                                 [1, {"max": 1}],
  "empty-lines/binary-expression":                      [1, {"max": 1}],
  "empty-lines/call-expression":                        [1, {"max": 1}],
  "empty-lines/class-body":                             [1, {"max": 1}],
  "empty-lines/conditional-expression":                 [1, {"max": 1}],
  "empty-lines/do-while-statement":                     [1, {"max": 1}],
  "empty-lines/export":                                 [1, {"max": 1}],
  "empty-lines/export-named-declaration":               [1, {"max": 1}],
  "empty-lines/for":                                    [1, {"max": 1}],
  "empty-lines/for-in-statement":                       [1, {"max": 1}],
  "empty-lines/for-of-statement":                       [1, {"max": 1}],
  "empty-lines/for-statement":                          [1, {"max": 1}],
  "empty-lines/function":                               [1, {"max": 1}],
  "empty-lines/function-declaration":                   [1, {"max": 1}],
  "empty-lines/function-expression":                    [1, {"max": 1}],
  "empty-lines/function-parameters":                    [1, {"max": 1}],
  "empty-lines/if":                                     [1, {"max": 1}],
  "empty-lines/if-statement":                           [1, {"max": 1}],
  "empty-lines/import":                                 [1, {"max": 1}],
  "empty-lines/import-declaration":                     [1, {"max": 1}],
  "empty-lines/import-expression":                      [1, {"max": 1}],
  "empty-lines/logical-expression":                     [1, {"max": 1}],
  "empty-lines/object":                                 [1, {"max": 1}],
  "empty-lines/object-assignment":                      [1, {"max": 1}],
  "empty-lines/object-expression":                      [1, {"max": 1}],
  "empty-lines/object-pattern":                         [1, {"max": 1}],
  "empty-lines/program":                                [1, {"max": 1}],
  "empty-lines/return":                                 [1, {"max": 1}],
  "empty-lines/return-statement":                       [1, {"max": 1}],
  "empty-lines/switch":                                 [1, {"max": 1}],
  "empty-lines/switch-case":                            [1, {"max": 1}],
  "empty-lines/switch-statement":                       [1, {"max": 1}],
  "empty-lines/ternary":                                [1, {"max": 1}],
  "empty-lines/try":                                    [1, {"max": 1}],
  "empty-lines/try-statement":                          [1, {"max": 1}],
  "empty-lines/while":                                  [1, {"max": 1}],
  "empty-lines/while-statement":                        [1, {"max": 1}],

  "empty-lines/jsx":                                    [1, {"max": 1}],
  "empty-lines/jsx-closing":                            [1, {"max": 1}],
  "empty-lines/jsx-closing-element":                    [1, {"max": 1}],
  "empty-lines/jsx-closing-fragment":                   [1, {"max": 1}],
  "empty-lines/jsx-element":                            [1, {"max": 1}],
  "empty-lines/jsx-empty-expression":                   [1, {"max": 1}],
  "empty-lines/jsx-expression-container":               [1, {"max": 1}],
  "empty-lines/jsx-fragment":                           [1, {"max": 1}],
  "empty-lines/jsx-opening":                            [1, {"max": 1}],
  "empty-lines/jsx-opening-element":                    [1, {"max": 1}],
  "empty-lines/jsx-opening-fragment":                   [1, {"max": 1}],
  "empty-lines/jsx-spread-attribute":                   [1, {"max": 1}],

  "empty-lines/ts-interface":                           [1, {"max": 1}],
  "empty-lines/ts-interface-declaration":               [1, {"max": 1}],
  "empty-lines/ts-type":                                [1, {"max": 1}],
  "empty-lines/ts-type-alias-declaration":              [1, {"max": 1}],
  "empty-lines/ts-type-literal":                        [1, {"max": 1}],


  "empty-lines/_accessor-property":                     [1, {"max": 1}],
  "empty-lines/_assignment-expression":                 [1, {"max": 1}],
  "empty-lines/_assignment-pattern":                    [1, {"max": 1}],
  "empty-lines/_await-expression":                      [1, {"max": 1}],
  "empty-lines/_block-statement":                       [1, {"max": 1}],
  "empty-lines/_break-statement":                       [1, {"max": 1}],
  "empty-lines/_catch-clause":                          [1, {"max": 1}],
  "empty-lines/_chain-expression":                      [1, {"max": 1}],
  "empty-lines/_class-declaration":                     [1, {"max": 1}],
  "empty-lines/_class-expression":                      [1, {"max": 1}],
  "empty-lines/_continue-statement":                    [1, {"max": 1}],
  "empty-lines/_debugger-statement":                    [1, {"max": 1}],
  "empty-lines/_decorator":                             [1, {"max": 1}],
  "empty-lines/_empty-statement":                       [1, {"max": 1}],
  "empty-lines/_export-all-declaration":                [1, {"max": 1}],
  "empty-lines/_export-default-declaration":            [1, {"max": 1}],
  "empty-lines/_export-specifier":                      [1, {"max": 1}],
  "empty-lines/_expression-statement":                  [1, {"max": 1}],
  "empty-lines/_identifier":                            [1, {"max": 1}],
  "empty-lines/_import-attribute":                      [1, {"max": 1}],
  "empty-lines/_import-default-specifier":              [1, {"max": 1}],
  "empty-lines/_import-namespace-specifier":            [1, {"max": 1}],
  "empty-lines/_import-specifier":                      [1, {"max": 1}],
  "empty-lines/_labeled-statement":                     [1, {"max": 1}],
  "empty-lines/_literal":                               [1, {"max": 1}],
  "empty-lines/_member-expression":                     [1, {"max": 1}],
  "empty-lines/_meta-property":                         [1, {"max": 1}],
  "empty-lines/_method-definition":                     [1, {"max": 1}],
  "empty-lines/_new-expression":                        [1, {"max": 1}],
  "empty-lines/_private-identifier":                    [1, {"max": 1}],
  "empty-lines/_property":                              [1, {"max": 1}],
  "empty-lines/_property-definition":                   [1, {"max": 1}],
  "empty-lines/_rest-element":                          [1, {"max": 1}],
  "empty-lines/_sequence-expression":                   [1, {"max": 1}],
  "empty-lines/_spread-element":                        [1, {"max": 1}],
  "empty-lines/_static-block":                          [1, {"max": 1}],
  "empty-lines/_super":                                 [1, {"max": 1}],
  "empty-lines/_tagged-template-expression":            [1, {"max": 1}],
  "empty-lines/_template-element":                      [1, {"max": 1}],
  "empty-lines/_template-literal":                      [1, {"max": 1}],
  "empty-lines/_this-expression":                       [1, {"max": 1}],
  "empty-lines/_throw-statement":                       [1, {"max": 1}],
  "empty-lines/_unary-expression":                      [1, {"max": 1}],
  "empty-lines/_update-expression":                     [1, {"max": 1}],
  "empty-lines/_variable-declaration":                  [1, {"max": 1}],
  "empty-lines/_variable-declarator":                   [1, {"max": 1}],
  "empty-lines/_with-statement":                        [1, {"max": 1}],
  "empty-lines/_yield-expression":                      [1, {"max": 1}],

  "empty-lines/_jsx-attribute":                         [1, {"max": 1}],
  "empty-lines/_jsx-identifier":                        [1, {"max": 1}],
  "empty-lines/_jsx-member-expression":                 [1, {"max": 1}],
  "empty-lines/_jsx-namespaced-name":                   [1, {"max": 1}],
  "empty-lines/_jsx-spread-child":                      [1, {"max": 1}],
  "empty-lines/_jsx-text":                              [1, {"max": 1}],

  "empty-lines/_ts-abstract-accessor-property":         [1, {"max": 1}],
  "empty-lines/_ts-abstract-keyword":                   [1, {"max": 1}],
  "empty-lines/_ts-abstract-method-definition":         [1, {"max": 1}],
  "empty-lines/_ts-abstract-property-definition":       [1, {"max": 1}],
  "empty-lines/_ts-any-keyword":                        [1, {"max": 1}],
  "empty-lines/_ts-array-type":                         [1, {"max": 1}],
  "empty-lines/_ts-as-expression":                      [1, {"max": 1}],
  "empty-lines/_ts-async-keyword":                      [1, {"max": 1}],
  "empty-lines/_ts-big-int-keyword":                    [1, {"max": 1}],
  "empty-lines/_ts-boolean-keyword":                    [1, {"max": 1}],
  "empty-lines/_ts-call-signature-declaration":         [1, {"max": 1}],
  "empty-lines/_ts-class-implements":                   [1, {"max": 1}],
  "empty-lines/_ts-conditional-type":                   [1, {"max": 1}],
  "empty-lines/_ts-construct-signature-declaration":    [1, {"max": 1}],
  "empty-lines/_ts-constructor-type":                   [1, {"max": 1}],
  "empty-lines/_ts-declare-function":                   [1, {"max": 1}],
  "empty-lines/_ts-declare-keyword":                    [1, {"max": 1}],
  "empty-lines/_ts-empty-body-function-expression":     [1, {"max": 1}],
  "empty-lines/_ts-enum-body":                          [1, {"max": 1}],
  "empty-lines/_ts-enum-declaration":                   [1, {"max": 1}],
  "empty-lines/_ts-enum-member":                        [1, {"max": 1}],
  "empty-lines/_ts-export-assignment":                  [1, {"max": 1}],
  "empty-lines/_ts-export-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-external-module-reference":          [1, {"max": 1}],
  "empty-lines/_ts-function-type":                      [1, {"max": 1}],
  "empty-lines/_ts-import-equals-declaration":          [1, {"max": 1}],
  "empty-lines/_ts-import-type":                        [1, {"max": 1}],
  "empty-lines/_ts-index-signature":                    [1, {"max": 1}],
  "empty-lines/_ts-indexed-access-type":                [1, {"max": 1}],
  "empty-lines/_ts-infer-type":                         [1, {"max": 1}],
  "empty-lines/_ts-instantiation-expression":           [1, {"max": 1}],
  "empty-lines/_ts-interface-body":                     [1, {"max": 1}],
  "empty-lines/_ts-interface-heritage":                 [1, {"max": 1}],
  "empty-lines/_ts-intersection-type":                  [1, {"max": 1}],
  "empty-lines/_ts-intrinsic-keyword":                  [1, {"max": 1}],
  "empty-lines/_ts-literal-type":                       [1, {"max": 1}],
  "empty-lines/_ts-mapped-type":                        [1, {"max": 1}],
  "empty-lines/_ts-method-signature":                   [1, {"max": 1}],
  "empty-lines/_ts-module-block":                       [1, {"max": 1}],
  "empty-lines/_ts-module-declaration":                 [1, {"max": 1}],
  "empty-lines/_ts-named-tuple-member":                 [1, {"max": 1}],
  "empty-lines/_ts-namespace-export-declaration":       [1, {"max": 1}],
  "empty-lines/_ts-never-keyword":                      [1, {"max": 1}],
  "empty-lines/_ts-non-null-expression":                [1, {"max": 1}],
  "empty-lines/_ts-null-keyword":                       [1, {"max": 1}],
  "empty-lines/_ts-number-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-object-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-optional-type":                      [1, {"max": 1}],
  "empty-lines/_ts-parameter-property":                 [1, {"max": 1}],
  "empty-lines/_ts-private-keyword":                    [1, {"max": 1}],
  "empty-lines/_ts-property-signature":                 [1, {"max": 1}],
  "empty-lines/_ts-protected-keyword":                  [1, {"max": 1}],
  "empty-lines/_ts-public-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-qualified-name":                     [1, {"max": 1}],
  "empty-lines/_ts-readonly-keyword":                   [1, {"max": 1}],
  "empty-lines/_ts-rest-type":                          [1, {"max": 1}],
  "empty-lines/_ts-satisfies-expression":               [1, {"max": 1}],
  "empty-lines/_ts-static-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-string-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-symbol-keyword":                     [1, {"max": 1}],
  "empty-lines/_ts-template-literal-type":              [1, {"max": 1}],
  "empty-lines/_ts-this-type":                          [1, {"max": 1}],
  "empty-lines/_ts-tuple-type":                         [1, {"max": 1}],
  "empty-lines/_ts-type-annotation":                    [1, {"max": 1}],
  "empty-lines/_ts-type-assertion":                     [1, {"max": 1}],
  "empty-lines/_ts-type-operator":                      [1, {"max": 1}],
  "empty-lines/_ts-type-parameter":                     [1, {"max": 1}],
  "empty-lines/_ts-type-parameter-declaration":         [1, {"max": 1}],
  "empty-lines/_ts-type-parameter-instantiation":       [1, {"max": 1}],
  "empty-lines/_ts-type-predicate":                     [1, {"max": 1}],
  "empty-lines/_ts-type-query":                         [1, {"max": 1}],
  "empty-lines/_ts-type-reference":                     [1, {"max": 1}],
  "empty-lines/_ts-undefined-keyword":                  [1, {"max": 1}],
  "empty-lines/_ts-union-type":                         [1, {"max": 1}],
  "empty-lines/_ts-unknown-keyword":                    [1, {"max": 1}],
  "empty-lines/_ts-void-keyword":                       [1, {"max": 1}]
}

▎ESLint: v9.x

// eslint.config.mjs
import pluginEmptylines from "eslint-plugin-empty-lines";
// @note   -> there is no 'recommended' configuration export as of yet
// @common -> eslint-plugin-empty-lines/dist/index.cjs

export default [{
  // ... other configurations
  plugins: {
    "empty-lines": pluginEmptylines,
  },
  // reasonable defaults; pick/choose/modify all, one, or none
  rules: {
    "empty-lines/array":                [1, {"max": 0}],
    "empty-lines/array-assignment":     [1, {"max": 0}],
    "empty-lines/binary":               [1, {"max": 0}],
    "empty-lines/export":               [1, {"max": 0}],
    "empty-lines/function-parameters":  [1, {"max": 0}],
    "empty-lines/import":               [1, {"max": 0}],
    "empty-lines/object":               [1, {"max": 0}],
    "empty-lines/object-assignment":    [1, {"max": 0}],
    "empty-lines/return":               [1, {"max": 0}],
    "empty-lines/ternary":              [1, {"max": 0}],

    "empty-lines/for":                  [1, {"max": 1}],
    "empty-lines/if":                   [1, {"max": 1}],
    "empty-lines/switch":               [1, {"max": 1}],
    "empty-lines/try":                  [1, {"max": 1}],
    "empty-lines/while":                [1, {"max": 1}],

    "empty-lines/class-body":           [1, {"max": 2}],
    "empty-lines/function":             [1, {"max": 2}],
    "empty-lines/program":              [1, {"max": 3}],

    // jsx
    "empty-lines/jsx-empty-expression": [1, {"max": 0}],
    "empty-lines/jsx-opening-element":  [1, {"max": 0}],
    "empty-lines/jsx-opening-fragment": [2, {"max": -1, "inline":""}],
    "empty-lines/jsx-closing":          [2, {"max": -1, "inline":""}],

    // typescript
    "empty-lines/ts-interface":         [1, {"max": 0}],
    "empty-lines/ts-type":              [1, {"max": 0}]
  }
}];

▎ESLint: v8.x

// .eslintrc.js
module.exports = {
  // ... other configurations
  plugins: ["empty-lines"],
  rules: {
    "empty-lines/array": [1, {"max": 0}]
  },
};

Rules

For more details and examples, go to ./docs/all.md or click into a specific rule target below

simplified for brevity–for the full spec visit: typescript-eslint.io/.../ast-spec

⟳=recursive
■=tested-rule
└── ■ program ⟳
    ├── <Declarations>
    │   ├── ■ import-declaration
    │   │   ├── import-specifier
    │   │   ├── import-default-specifier
    │   │   ├── import-namespace-specifier
    │   │   └── import-attribute
    │   ├── export-all-declaration
    │   ├── export-default-declaration
    │   ├── ■ export
    │   │   └── export-specifier
    │   ├── variable-declaration
    │   │   └── variable-declarator
    │   │       ├── ■ array-pattern
    │   │       ├── ■ object-pattern
    │   │       └── assignment-pattern
    │   ├── ■ function-declaration ⟳
    │   │   └── block-statement ⟳
    │   └── class-declaration ⟳
    │       ├── decorator
    │       └── ■ class-body ⟳
    │           ├── method-definition ⟳
    │           │   └── block-statement ⟳
    │           ├── propertyDefinition
    │           ├── accessor-property
    │           └── static-block
    │
    ├── <Statements>
    │   ├── block-statement ⟳
    │   ├── expression-statement
    │   │   ├── assignment-expression ⟳
    │   │   ├── ■ binary-expression ⟳
    │   │   ├── ■ logical-expression ⟳
    │   │   ├── ■ conditional-expression ⟳
    │   │   ├── sequence-expression ⟳
    │   │   ├── ■ call-expression ⟳
    │   │   │   └── identifier
    │   │   ├── new-expression ⟳
    │   │   │   └── identifier
    │   │   ├── chain-expression
    │   │   ├── tagged-template-expression
    │   │   └── ■ arrow-function-expression ⟳
    │   │       └── block-statement ⟳
    │   ├── break-statement
    │   ├── continue-statement
    │   ├── debugger-statement
    │   ├── empty-statement
    │   ├── labeled-statement
    │   ├── ■ return-statement
    │   └── throw-statement
    │
    ├── <Control Flow>
    │   ├── ■ if-statement ⟳
    │   │   ├── block-statement ⟳
    │   │   └── ElseStatement
    │   │       └── block-statement ⟳
    │   ├── ■ switch-statement ⟳
    │   │   └── ■ switch-case ⟳
    │   │       └── block-statement ⟳
    │   ├── ■ try-statement ⟳
    │   │   ├── block-statement ⟳
    │   │   ├── catch-clause ⟳
    │   │   │   └── block-statement ⟳
    │   │   └── FinallyStatement
    │   │       └── block-statement ⟳
    │   ├── ■ while-statement ⟳
    │   │   └── block-statement ⟳
    │   ├── ■ do-while-statement ⟳
    │   │   └── block-statement ⟳
    │   ├── ■ for-statement ⟳
    │   │   └── block-statement ⟳
    │   ├── ■ for-in-statement ⟳
    │   │   └── block-statement ⟳
    │   ├── ■ for-of-statement ⟳
    │   │   └── block-statement ⟳
    │   └── with-statement ⟳
    │       └── block-statement ⟳
    │
    ├── <Expressions>
    │   ├── await-expression ⟳
    │   ├── yield-expression ⟳
    │   ├── unary-expression
    │   ├── update-expression
    │   ├── member-expression ⟳
    │   ├── meta-property
    │   ├── this-expression
    │   ├── super
    │   ├── private-identifier
    │   ├── ■ object-expression ⟳
    │   │   ├── property ⟳
    │   │   │   ├── literal
    │   │   │   ├── identifier
    │   │   │   └── ComputedPropertyName
    │   │   └── accessor-property
    │   ├── ■ array-expression ⟳
    │   │   └── spread-element ⟳
    │   └── template-literal ⟳
    │       └── template-element
    │
    ├── <JSX>
    │   ├── ■ jsx-element ⟳
    │   │   ├── ■ jsx-opening-element
    │   │   │   └── jsx-attribute ⟳
    │   │   ├── ■ jsx-closing-element
    │   │   ├── jsx-identifier
    │   │   └── jsx-member-expression
    │   ├── ■ jsx-fragment ⟳
    │   │   ├── ■ jsx-opening-fragment
    │   │   └── ■ jsx-closing-fragment
    │   ├── ■ jsx-expression-container
    │   │   └── ■ jsx-empty-expression
    │   ├── ■ jsx-spread-attribute ⟳
    │   ├── jsx-spread-child ⟳
    │   ├── jsx-namespaced-name
    │   └── jsx-text
    │
    ├── <Misc>
    │    ├── ■ import-expression
    │    ├── rest-element
    │    └── ■ function-expression ⟳
    │        └── block-statement ⟳
    │
    └── <TypeScript>
        ├── ts-abstract-accessor-property
        ├── ts-abstract-method-definition ⟳
        ├── ts-abstract-property-definition
        ├── ts-array-type ⟳
        ├── ts-as-expression
        ├── ts-class-implements ⟳
        ├── ts-conditional-type ⟳
        ├── ts-constructor-type ⟳
        ├── ts-construct-signature-declaration ⟳
        ├── ts-declare-function
        ├── ts-empty-body-function-expression
        ├── ts-enum-body ⟳
        │   ├── ts-enum-declaration
        │   └── ts-enum-member
        ├── ts-export-assignment
        ├── ts-external-module-reference
        ├── ts-function-type ⟳
        ├── ts-import-equals-declaration
        ├── ts-import-type
        ├── ts-indexed-access-type
        ├── ts-index-signature
        ├── ts-infer-type
        ├── ts-instantiation-expression
        ├── ts-interface-body ⟳
        ├── ts-interface-heritage
        ├── ts-intersection-type ⟳
        ├── ■ ts-interface-declaration ⟳
        ├── ts-literal-type ⟳
        ├── ts-mapped-type ⟳
        ├── ts-method-signature ⟳
        ├── ts-module-block ⟳
        │   └── ts-module-declaration
        ├── ts-named-tuple-member
        ├── ts-namespace-export-declaration
        ├── ts-non-null-expression
        ├── ts-optional-type
        ├── ts-parameter-property
        ├── ts-property-signature
        ├── ts-qualified-name
        ├── ts-rest-type
        ├── ts-satisfies-expression
        ├── ts-template-literal-type ⟳
        ├── ts-this-type
        ├── ts-tuple-type ⟳
        │   └── ts-named-tuple-member
        ├── ■ ts-type-alias-declaration
        ├── ts-type-annotation
        ├── ts-type-assertion
        ├── ■ ts-type-literal ⟳
        ├── ts-type-operator
        ├── ts-type-parameter
        │   ├── ts-type-parameterDeclaration
        │   └── ts-type-parameterInstantiation
        ├── ts-type-predicate
        ├── ts-type-query
        ├── ts-type-reference
        ├── ts-union-type ⟳
        │
        └── <Keyword>
            ├── ts-abstract-keyword
            ├── ts-any-keyword
            ├── ts-async-keyword
            ├── ts-big-int-keyword
            ├── ts-boolean-keyword
            ├── ts-declare-keyword
            ├── ts-export-keyword
            ├── ts-intrinsic-keyword
            ├── ts-never-keyword
            ├── ts-null-keyword
            ├── ts-number-keyword
            ├── ts-object-keyword
            ├── ts-private-keyword
            ├── ts-protected-keyword
            ├── ts-public-keyword
            ├── ts-readonly-keyword
            ├── ts-static-keyword
            ├── ts-string-keyword
            ├── ts-symbol-keyword
            ├── ts-undefined-keyword
            ├── ts-unknown-keyword
            └── ts-void-keyword

▎ General

▎ JSX

▎ TypeScript

Untested Rules

For completeness, this plugin includes all ESLint rules, including non-applicable rules, like debugger

[!NOTE] ⊳ Despite the "untested" label, all rules follow the same logic and should work as intended ⊳ To promote an un-tested rule to a tested rule, check out the largely automated steps here

▎ General

▎ JSX

▎ TypeScript

Options

max

The max option specifies the maximum number of consecutive (empty) lines allowed within the scope

default: 3

/* eslint empty-lines/function: ["warn", {"max":0}] */
const aFactorium = (num = 1, fac = 2) => {

  return num ^ fac;
};

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const aFactorium = (num = 1, fac = 2) => {
  return num ^ fac;
};

[!IMPORTANT] Rule execution is independent; ergo, if two rules have the same scope, both rules will report errors (example below)

/*eslint empty-lines/program:  [1, {"max": 0}]*/
/*eslint empty-lines/function: [1, {"max": 0}]*/
const warningsOnErrors = (problematic: string) => {

  return (): string => {

    return problematic;
  };
};
/* > ESLint Output
   4:1  error  .../program : contains 1 empty lines, the maximum is 0
   4:1  error  .../function: contains 1 empty lines, the maximum is 0
   6:1  error  .../program : contains 1 empty lines, the maximum is 0
   6:1  error  .../function: contains 1 empty lines, the maximum is 0
*/

whiteSpaceIsEmpty

The whiteSpaceIsEmpty option controls how 'empty' lines are defined. By default, a line is only considered empty if it contains no characters and no whitespace. However, if enabled (whiteSpaceIsEmpty: true), lines that contain only whitespace (spaces and/or tabulators) are also considered empty.

default: false

/* eslint empty-lines/function: [1, {"max":0,"whiteSpaceIsEmpty":true}] */

const thisFunctionIsFullOfWhiteSpace = (trust = 0, me = 1) => {
                                                                                
                                                                                
  return trust + me;
};

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const thisFunctionIsFullOfWhiteSpace = (trust = 0, me = 1) => {
  return trust + me;
};

inline

The inline option is a conditional option that is only enabled if all are true:

  1. The inline option key is defined with a value of: true or string (" ")
  2. The max value is set to zero (0)
  3. One or more empty lines are present

While technically correct, this this behavior is probably not what you want/expect; check force/hard inline

With inline: ""

/* eslint empty-lines/jsx-closing: ["warn", {"max":0, inline:""}] */
const NoSpaceForLife = () => <>
    <h1>No Space In Fragments Please</h1>
  </

    >;

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧   WITH INLINE   ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const NoSpaceForLife = () => <>
    <h1>No Space In Fragments Please</h1>
  </>;

Without inline

/* eslint empty-lines/jsx-closing: ["warn", {"max":0}] */
const NoSpaceForLife = () => <>
    <h1>No Space In Fragments Please</h1>
  </

    >;

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧  WITHOUT INLINE  ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const NoSpaceForLife = () => <>
    <h1>No Space In Fragments Please</h1>
  </
    >;

What Not to Do

/* eslint empty-lines/function: ["warn", {"max":0,inline:"\n  // yo, tom, clean up your damn code\n  "}] */
const sillyPad = (val = '', pad = 1) => {
  const res = String(val).padEnd(pad, ' ');

  return res;
}

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧  WHAT NOT TO DO  ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const sillyPad = (val = '', pad = 1) => {
  const res = String(val).padEnd(pad, ' ');
  // yo, tom, clean up your damn code
  return res;
}

▎ Force/Hard inline

Safety comes first, so this plugin only removes empty lines, but you can disable this safety feature by setting max set to -1 alongside an inline value to redefine 'empty' as a more nuanced value of negative none

default: none

To understand the use case, check the following example, which does not raise any ESLint errors:

/* eslint empty-lines/jsx-closing: ["warn", {"max":0,"inline":""}] */
const HowManyIsTooMany = () => {
  return (<div>
    <h1>Negative None</h1
    >
  </div
  >);
};

No empty lines == no errors, but what you probably want/perfer is the following:

/* eslint empty-lines/jsx-closing: ["warn", {"max":-1,"inline":""}] */
const HowManyIsTooMany = () => {
  return (<div>
    <h1>Negative None</h1
    >
  </div
  >);
};

/* ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ 🟢 FIXED 🟢 ↧ ↧ ↧ */

const HowManyIsTooMany = () => {
  return (<div>
    <h1>Negative None</h1>
  </div>);
};

[!CAUTION] DO NOT use within any whitespace sensitive context/scope; it's only desinged for simple use-cases like jsx-closing

/* eslint empty-lines/program: ["warn", {"max":-1,"inline":""}] */
const ForceDeletesWhiteSpace = () => <>
  <h1>
  Oh No! All The WhiteSpace Is Gone!
  </h1>
</>;

/* ↧ ↧ ↧ 🔴 FIXED, BUT BAD 🔴 ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ ↧ 🔴 FIXED, BUT BAD 🔴 ↧ ↧ ↧ */

const ForceDeletesWhiteSpace = () => <><h1>OhNo!AllTheWhiteSpaceIsGone!</h1></>;

Development/Contributing

To get started, ensure you have satisfied the Bun and Make build dependencies.

[!NOTE] This plugin takes care of most documentation and configuration tasks automatically, so it's best to follow the format and conventions of the existing rules. However, if the make build step throws you any unexpected curve balls or problems, feel free to submit your pull request anyway and let me know.

▎ Pull Request Steps

  1. Commit your changes and code
  2. Run make build to lint, build, update, and regenerate all rules, tests, and their documentation
  3. Assuming everything checks out
  4. Commit any auto-generated documentation changes
  5. Push your branch to the repository
  6. Submit a pull request

▎ Promoting an Untested Rule to a Tested Rule

  1. Run make promote_untested (or manually handle the scaffolding and setup)
  2. Enter the name of an untested rule, such as rule-catch-clause
  3. Write tests for the now-promoted rule in ./src/tested/<id>.test.ts (ideally, 4+ test cases for valid and invalid)
  4. Commit changes, run make build to regenerate index.ts paths, Makefile tests, and docs
  5. Submit a pull request

[!TIP] In terms of enjoyment, writing and debugging ESLint tests ranks somewhere between a root canal and a tax audit, but you can make the process marginally less painful by using the test debug flag (DEBUG=1 make test) and, if necessary, bunx patch-package to pretty diff print failed tests.

▎Makefile Reference

# USAGE
  make [flags...] <target>

# TARGET
  -------------------
   release               clean, setup, build, lint, test, aok (everything but the kitchen sink)
  -------------------
   build                 builds the .{js,d.ts} (skips: lint, test, and .min.* build)
   build_cjs             builds the .cjs export
   build_esm             builds the .js (esm) export
   build_declarations    builds typescript .d.{ts,mts,cts} declarations
  -------------------
   install               installs dependencies via bun
   update                updates dependencies
   update_dry            lists dependencies that would be updated via 'make update'
  -------------------
   lint                  lints via tsc & eslint
   lint_eslint           lints via eslint
   lint_eslint_fix       lints and auto-fixes via eslint --fix
   lint_tsc              lints via tsc
  -------------------
   promote_untested      promote an untested target rule (moves/updates file/test files into src)
   regen                 update/regenerate index/makefile and makes all docs for all rules
   regen_dry             update/regenerate index/makefile and makes all docs for all rules
  -------------------
   test                  runs all tests for all rules
  -------------------
   help                  displays (this) help screen

# FLAGS
  -------------------
   BUN                   [? ] bun build flag(s) (e.g: make BUN="--banner='// bake until golden brown'")
  -------------------
   CJS                   [?1] builds the cjs (CommonJS) target on 'make release'
   EXE                   [?js|mjs] default esm build extension
   TAR                   [?0] build target env (-1=bun, 0=node, 1=dom, 2=dom+iife, 3=dom+iife+userscript)
   MIN                   [?1] builds minified (*.min.{mjs,cjs,js}) targets on 'make release'
  -------------------
   BAIL                  [?1] fail fast (bail) on the first test or lint error
   ENV                   [?DEV|PROD|TEST] sets the 'ENV' & 'IS_*' static build variables (else auto-set)
   TEST                  [?0] sets the 'IS_TEST' static build variable (always 1 if test target)
   WATCH                 [?0] sets the '--watch' flag for bun/tsc (e.g: WATCH=1 make test)
  -------------------
   DEBUG                 [?0] enables verbose logging and sets the 'IS_DEBUG' static build variable
   QUIET                 [?0] disables pretty-printed/log target (INIT/DONE) info
   NO_COLOR              [?0] disables color logging/ANSI codes
   TIMING                [0|1] eslint perf timing flag

License

MIT License

Copyright (c) 2025 te <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.