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>)
# 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π’ 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>);
};Add the plugin and rule to your ESLint (eslint.config.mjs, .eslintrc.js, .eslintrc.json, etc.)
Every Rule in JSON (click to reveal)
{
"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.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}]
}
}];// .eslintrc.js
module.exports = {
// ... other configurations
plugins: ["empty-lines"],
rules: {
"empty-lines/array": [1, {"max": 0}]
},
};For more details and examples, go to ./docs/all.md or click into a specific rule target below
Scope/Hierarchy Reference Tree (click to reveal)
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
/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(targets children)/jsx-closing(targets children)/jsx-empty-expression/jsx-expression-container/jsx-opening(targets children)/jsx-spread-attribute
/ts-interface-declaration- alias:
/ts-interface
- alias:
/ts-type-alias-declaration- anything that starts withtype- alias:
/ts-type
- alias:
/ts-type-literal
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
All Untested Rules (click to reveal)
/_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-attribute/_jsx-identifier/_jsx-member-expression/_jsx-namespaced-name/_jsx-spread-child/_jsx-text
/_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
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
*/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;
};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;
}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></>;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.
- 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
- 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-package to pretty diff print failed tests.
# 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
MIT License
Copyright (c) 2025 te <legal@fetchTe.com>
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.