eslint-plugin-empty-lines
v1.0.0
Published
A stylistic ESLint spacing rule that limits consecutive empty lines by scope
Maintainers
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-linesQuickStart
🟢 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
/array-expression- the fancy name for an array['vino']- alias:
/array
- alias:
/array-pattern- otherwise known as an array assignmentconst [a, b, c] = array;- alias:
/array-assignment
- alias:
/arrow-function-expression/binary- all the maths, mathy, and logic operators:+ - * / > ^ < % & ? | = && || ??(implements TypeScript def)/binary-expression- all the maths & mathy operators: arithmetic, assignment, bitwise, comparison+ - * / > ^ < % & ? | =/call-expression- a function/method call likeconsole.log('im a call expression')/class-body/conditional-expression- conditional (ternary) operator- alias:
/ternary
- alias:
/export- targetsexport {these, typesOf, exporters}/export-named-declaration/for(targets children)/function- targets the/block-statement(body) of all functions/function-declaration/function-expression- a non-hoisted function that is named or anonymous/function-parameters- designed to work work in tandem with the/functionrule/if-statement- alias:
/if
- alias:
/import(targets children)/logical-expression/object-expression- the fancy name for an object{settee: 1}- alias:
/object
- alias:
/object-pattern- otherwise known as an object assignmentconst {a,z} = obj- alias:
/object-assignment
- alias:
/program- targets whole file (two entire milliseconds faster than ESLint'sno-multiple-empty-linesrule)/return-statement- alias:
/return
- alias:
/switch-case/switch-statement- alias:
/switch
- alias:
/try-statement- alias:
/try
- alias:
/while(targets children)
▎ JSX
/jsx(targets children)/jsx-closing(targets children)/jsx-empty-expression/jsx-expression-container/jsx-opening(targets children)/jsx-spread-attribute
▎ TypeScript
/ts-interface-declaration- alias:
/ts-interface
- alias:
/ts-type-alias-declaration- anything that starts withtype- alias:
/ts-type
- alias:
/ts-type-literal
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
/_accessor-property/_assignment-expression/_assignment-pattern/_await-expression/_block-statement/_break-statement/_catch-clause/_chain-expression/_class-declaration/_class-expression/_continue-statement/_debugger-statement/_decorator/_empty-statement/_export-all-declaration/_export-default-declaration/_export-specifier/_expression-statement/_identifier/_import-attribute/_import-default-specifier/_import-namespace-specifier/_import-specifier/_labeled-statement/_literal/_member-expression/_meta-property/_method-definition/_new-expression/_private-identifier/_property/_property-definition/_rest-element/_sequence-expression/_spread-element/_static-block/_super/_tagged-template-expression/_template-element/_template-literal/_this-expression/_throw-statement/_unary-expression/_update-expression/_variable-declaration/_variable-declarator/_with-statement/_yield-expression
▎ JSX
/_jsx-attribute/_jsx-identifier/_jsx-member-expression/_jsx-namespaced-name/_jsx-spread-child/_jsx-text
▎ TypeScript
/_ts-abstract-accessor-property/_ts-abstract-keyword/_ts-abstract-method-definition/_ts-abstract-property-definition/_ts-any-keyword/_ts-array-type/_ts-as-expression/_ts-async-keyword/_ts-big-int-keyword/_ts-boolean-keyword/_ts-call-signature-declaration/_ts-class-implements/_ts-conditional-type/_ts-construct-signature-declaration/_ts-constructor-type/_ts-declare-function/_ts-declare-keyword/_ts-empty-body-function-expression/_ts-enum-body/_ts-enum-declaration/_ts-enum-member/_ts-export-assignment/_ts-export-keyword/_ts-external-module-reference/_ts-function-type/_ts-import-equals-declaration/_ts-import-type/_ts-index-signature/_ts-indexed-access-type/_ts-infer-type/_ts-instantiation-expression/_ts-interface-body/_ts-interface-heritage/_ts-intersection-type/_ts-intrinsic-keyword/_ts-literal-type/_ts-mapped-type/_ts-method-signature/_ts-module-block/_ts-module-declaration/_ts-named-tuple-member/_ts-namespace-export-declaration/_ts-never-keyword/_ts-non-null-expression/_ts-null-keyword/_ts-number-keyword/_ts-object-keyword/_ts-optional-type/_ts-parameter-property/_ts-private-keyword/_ts-property-signature/_ts-protected-keyword/_ts-public-keyword/_ts-qualified-name/_ts-readonly-keyword/_ts-rest-type/_ts-satisfies-expression/_ts-static-keyword/_ts-string-keyword/_ts-symbol-keyword/_ts-template-literal-type/_ts-this-type/_ts-tuple-type/_ts-type-annotation/_ts-type-assertion/_ts-type-operator/_ts-type-parameter/_ts-type-parameter-declaration/_ts-type-parameter-instantiation/_ts-type-predicate/_ts-type-query/_ts-type-reference/_ts-undefined-keyword/_ts-union-type/_ts-unknown-keyword/_ts-void-keyword
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:
- The
inlineoption key is defined with a value of:trueor string (" ") - The
maxvalue is set to zero (0) - 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 buildstep throws you any unexpected curve balls or problems, feel free to submit your pull request anyway and let me know.
▎ Pull Request Steps
- Commit your changes and code
- Run
make buildto lint, build, update, and regenerate all rules, tests, and their documentation - Assuming everything checks out
- Commit any auto-generated documentation changes
- Push your branch to the repository
- Submit a pull request
▎ Promoting an Untested Rule to a Tested Rule
- Run
make promote_untested(or manually handle the scaffolding and setup) - Enter the name of an untested rule, such as
rule-catch-clause - Write tests for the now-promoted rule in
./src/tested/<id>.test.ts(ideally, 4+ test cases forvalidandinvalid) - Commit changes, run
make buildto regenerateindex.tspaths,Makefiletests, and docs - 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-packageto 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 flagLicense
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.