@bastidood/eslint-plugin-slopstopper
v0.6.0
Published
Custom lint rules against sloppy code, for both Oxlint and ESLint.
Readme
@bastidood/eslint-plugin-slopstopper
Custom lint rules against sloppy code. One plugin runs in both Oxlint (as a JS plugin) and ESLint.
Installation
pnpm add --save-dev @bastidood/eslint-plugin-slopstopper@bastidood/oxlint-config/core and @bastidood/eslint-config/core enable these rules. Install the plugin next to them.
Rules
Rules marked as derived from anti-slop are vendored from dmmulroy/anti-slop under the MIT License. THIRD-PARTY-NOTICES.md lists them with the license text.
| Rule | Reports |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| stepdown-order | A function or method declared above a function or statement in the same scope that uses it; autofixes the order |
| no-private-modifier | The TypeScript private modifier; autofixes to #private members |
| handle-or-rethrow | A catch that swallows the error, logs and rethrows it, or leaves it unbound |
| needless-type-intersection | A type alias of object intersections that can be an interface ... extends |
| no-inferrable-return-type | An explicit return type that TypeScript can infer; suggests removing it |
| no-type-predicates | is and asserts type predicates, which TypeScript does not check |
| no-positional-scalars | Too many positional parameters of one scalar kind (two booleans or three of another kind by default) |
| no-multiple-optional-parameters | Two or more optional parameters |
| prefer-switch | An if chain of two or more === comparisons against the same value |
| no-equality-chain | An \|\| chain of two or more === comparisons against the same value that no prefer-switch chain claims |
| prefer-de-morgan | A logical chain that De Morgan's law rewrites with fewer negations, where another rule then removes the outer !; autofixes the rewrite |
| no-negated-group | A ! applied to an && or \|\| group, outside the positions that prefer-de-morgan owns; autofixes with De Morgan's law |
| no-negated-quantifier-callback | A .some or .every callback that returns !p; rewrites to the dual quantifier with one ! outside the call, autofixed where types prove the standard method |
| merge-exclusive-branches | Two adjacent if statements whose tests split on one condition and its negation; autofixes the merge into one if-else where safe |
| prefer-typeof-undefined | A === or !== comparison against undefined or void 0; autofixes to typeof x === 'undefined' |
| no-temporary-access | A field read off the unnamed result of a call, as in f().x; autofixes to a const destructuring where safe |
| no-plain-template-literal | An untagged template literal without expressions on one line; autofixes to a quoted string |
| no-alias-object-type | An object type literal nested in a type alias, other than the alias's whole type or a needless-type-intersection report |
| prefer-class-over-factory | A module-level function whose every return is an object literal with a function-valued member |
| prefer-prefix-increment | x += 1, x -= 1, or a postfix update whose value is discarded; autofixes to ++x or --x where x is proven to be a number |
| prefer-while-true | A for (;;) loop; autofixes to while (true) |
| destructure-on-sight | A parameter or const whose every use reads a static property off it; suggests destructuring it at the binding |
| no-array-filter-map | A .filter(…).map(…) or .map(…).filter(…) chain on a known array, which walks the elements twice (derived from anti-slop) |
| no-reduce-accumulator-copy | A copy of the .reduce accumulator with Object.assign, Array.from, or an array copy method on every step (derived from anti-slop) |
| no-conditional-empty-object-spread | An object spread of a conditional with an empty object branch, as in ...(c ? { v } : {}) (derived from anti-slop) |
| no-known-value-widening | An explicit type or assertion that discards the known type of a value, such as unknown, object, or an open dictionary; suggests satisfies for as (derived from anti-slop) |
| no-unknown-returns | An explicit return type that resolves to unknown or Promise<unknown> (derived from anti-slop) |
| no-unknown-type-aliases | A type alias that resolves to unknown (derived from anti-slop) |
| no-unsafe-dictionary-type | A dictionary type whose value type is unknown, any, object, {}, or a union or alias that holds one (derived from anti-slop) |
| no-untyped-empty-initializer | A binding without an annotation that has no initializer or starts as [], {}, or null, a new Map(), new WeakMap(), or new Set() without type arguments, or an assertion on an empty literal |
| prefer-query-key-context | A TanStack Query queryFn that captures a value its sibling queryKey also holds, instead of reading it from the query function context |
| no-defaulted-query-key | A ??, \|\|, or defaulted binding in a TanStack Query queryKey, which shares a cache entry with a real value |
| no-standalone-mutate-async | A TanStack Query mutateAsync outside a Promise.all, allSettled, race, or any of concurrent mutations, tracked through renames and aliases |
| no-mutate-callback-cache-update | A QueryClient cache update in the per-call callbacks of a TanStack Query mutate, tracked through renames and aliases |
| prefer-table-atoms | A TanStack Table table.store snapshot or raw use, a store selector that reads one slice, a React useSelector on the store that returns a fresh literal without compare, a Svelte useSelector on the store, or a table.Subscribe without source or selector; suggests the slice atom |
| require-table-state-pairs | A TanStack Table state slice without its on<Slice>Change handler, or a handler without its slice |
| no-render-scoped-table-schema | A TanStack Table tableFeatures, createColumnHelper, or helper.columns call inside a component, hook, or Svelte instance script |
| require-exclusive-indeterminate | A TanStack Table getIsSomeRowsSelected() indeterminate value without && !getIsAllRowsSelected(); suggests the exclusion |
Compatibility
- Every rule uses the ESLint rule API, so the same rule runs in Oxlint and ESLint.
- The plugin is typed for Oxlint. In ESLint, use the rules through
@bastidood/eslint-config, which registers the plugin for you. - Rules check syntax only by default. Where a rule benefits from type information, it uses it when the linter provides TypeScript's program (typescript-eslint's
parserServices), and falls back to syntax otherwise. Oxlint's JS plugins do not provide type information, so the same code can pass Oxlint and still fail ESLint with typescript-eslint.
Configuration
no-positional-scalars reports a signature once any scalar kind reaches its threshold of positional parameters. The defaults are { boolean: 2, string: 3, number: 3, bigint: 3, symbol: 3 }; each threshold is at least 2:
{ 'slopstopper/no-positional-scalars': ['error', { string: 2 }] }Limitations
- Apart from the
no-positional-scalarsthresholds, rules are not configurable.
