@bravobit/eslint-plugin-waterfall
v2.0.2
Published
ESLint plugin that sorts imports and CSS/SCSS declarations by line length, longest first — the waterfall.
Maintainers
Readme
@bravobit/eslint-plugin-waterfall
ESLint plugin that sorts by line length, longest first — the visual "waterfall". It does this in two places:
waterfall/sort-imports— top-level imports in JS/TS files;waterfall/sort-declarations— declarations inside the braces of CSS and SCSS rules (there the waterfall runs the other way: shortest first), via the built-inwaterfall/csslanguage.
No grouping by origin, no alphabetizing, no options to study. Just the ladder.
// imports, after --fix
import {ChangeDetectionStrategy, Component, ElementRef, inject, OnInit} from '@angular/core';
import {BbAvatar, BbButton, BbFormControl, BbIcon} from '@bravobit/bb-foundation/elements';
import {toSignal} from '@angular/core/rxjs-interop';
import {RouterLink} from '@angular/router';// scss, after --fix — shortest first
.button {
color: white;
display: flex;
border-radius: 4px;
background-color: $color-primary;
&:hover {
cursor: pointer;
background-color: $color-primary-dark;
}
}The sort key for imports: length of the statement as one line (UTF-16 code
units), then — on a tie — the position of the from keyword (furthest right
first), then the original order (stable). Declarations sort the other way
around — shortest first, with the position of the : (furthest left first)
as tiebreaker — so a block widens toward its base.
Multiline statements are measured as one line but never reformatted.
Comments travel with their statement; license headers and directive comments
(eslint-disable, stylelint-disable, @ts-*, prettier-ignore, …) stay
exactly where they are and act as sort boundaries. Blank lines split
statements into independently sorted groups.
Install
npm install -D @bravobit/eslint-plugin-waterfallRequires ESLint ^9.15.0 || ^10.0.0 (flat config) and Node 20.19+, 22.13+ or 24+.
Usage
// eslint.config.js / eslint.config.mjs
import waterfall from '@bravobit/eslint-plugin-waterfall';
export default [
...waterfall.configs.recommended, // both rules as "warn", js/ts + css/scss
];CommonJS works identically (const waterfall = require('@bravobit/eslint-plugin-waterfall')).
Or configure by hand:
export default [
{
files: ['**/*.{js,ts}'],
plugins: { waterfall },
rules: { 'waterfall/sort-imports': 'warn' },
},
{
files: ['**/*.{css,scss}'],
plugins: { waterfall },
language: 'waterfall/css',
rules: { 'waterfall/sort-declarations': 'warn' },
},
];Rules
| Rule | Description |
| --- | --- |
| waterfall/sort-imports | Sort top-level imports by line length, longest first. |
| waterfall/sort-declarations | Sort CSS/SCSS declarations by line length, shortest first. |
Options
Deliberately minimal:
| Rule | Option | Default | Behaviour |
| --- | --- | --- | --- |
| sort-imports | pinSideEffectImports | true | import 'polyfill' keeps its position; the rest sorts around it. Turning this off can change runtime behaviour. |
| both | pinnedCommentPattern | – | Extra regex for comments that must never move, on top of the built-in directive list. |
Everything else is fixed behaviour: imports longest first, declarations shortest first, blank lines split groups, comments move with their statement, groups need at least two entries.
What sort-declarations will not touch
- SCSS variables (
$x: …) — order-dependent, they stay put and split groups. - At-rules (
@include,@extend, …) and nested rules — boundaries. - Duplicate properties in one group (
display: -webkit-box; display: flex;) — cascade fallbacks whose order is meaning, the whole group is left alone. - Anything the tolerant scanner does not recognise — unknown syntax becomes a boundary, never a rewrite.
Does this work with Prettier / stylelint?
At a normal printWidth, Prettier wraps long imports onto multiple lines and
the visual waterfall disappears (the rule still measures wrapped statements
correctly). Set printWidth high enough that imports stay on one line, or
accept the effect only where they do. Stylelint's ordering rules
(order/properties-alphabetical-order etc.) fight sort-declarations —
enable one, not both.
Sorting inside the import braces
waterfall/sort-imports orders whole lines, not the names inside { … }.
For alphabetical specifiers use ESLint core, which composes cleanly:
rules: {
'waterfall/sort-imports': 'warn',
'sort-imports': ['warn', { ignoreDeclarationSort: true, ignoreCase: true }],
}Why not eslint-plugin-perfectionist?
Perfectionist can sort by line length, but its fallbackSort cannot break
ties on the position of the from keyword — exactly the lines where the
waterfall is most visible. If you run both, disable
perfectionist/sort-imports and import/order.
Adopting in an existing codebase
The first --fix touches a lot of lines. Do it as a separate, mechanical
commit so reviews and git blame stay usable.
Non-goals
No grouping by category, no alphabetical sorting of statements, no blank-line
management, no reformatting (multiline ↔ single line), no deduplication, no
export … from sorting, no LESS/Stylus.
License
MIT © Bravobit
