utils/ts_config/eslint/naming.js
Krzysztof kuhy Rudnicki 128ede438e ts_config: @kuhyx/ts-config, the one lint/TS bar every TypeScript repo extends
Evil Martians' "ten anti-AI-slop moves" as a package: strict tsconfig base
(noUncheckedIndexedAccess, exactOptionalPropertyTypes), type-checked
typescript-eslint strict+stylistic, unicorn/sonarjs/perfectionist, behaviour
rules for tests (expect-expect, no-focused-tests), React (hooks, @eslint-react,
jsx-a11y strict, you-might-not-need-an-effect), Playwright, and layer
boundaries via eslint-plugin-boundaries 7 with the article's default policies.
Knip / jscpd / Stryker bases; Stryker carries no break threshold on purpose.

Plugins are dependencies, not devDependencies: a flat config imports them from
this package's own node_modules, so a consumer installing from the git tag
gets them or the lint cannot run. No build step, so the tag needs no prepare.

test/fixture is a mini repo with one planted violation per rule family;
test/expected.js names the rule that must catch each, and the suite runs the
real CLI over it (in a child process -- v8 coverage merges natively-loaded
and transformed copies of the same module into "uncovered"). 35 tests, 100%
branch coverage on the factories.

eslint/fast.js is the type-free subset for a per-edit hook: 0.52 s cold on
one file, measured against 1.28 s for base() without types -- importing a
plugin is the cost, so it loads none, nor base.js which imports them.

Probed under ESLint 10: jsx-a11y 6.10.2 (peer says <=9) and boundaries 7.2.0
both run; the fixture pins that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
2026-09-16 21:40:08 +02:00

32 lines
1.4 KiB
JavaScript

// Structural rules: a file's name tells you what it exports.
//
// The article's "filenames matching exports" move. Kebab-case files exporting
// PascalCase components is the house convention across every kuhyx web repo;
// a model that invents `UserCard.tsx` next to `user-list.tsx` has not read the
// tree, and the rule says so before review has to.
/** @type {import("eslint").Linter.Config[]} */
export const naming = [
{
rules: {
// Keep the rule, widen its vocabulary. `passesFilters(server, filter)`
// and `matchesQuery(...)` are predicates that read as English; forcing
// them to `isPassingFilters` would be worse prose in service of a prefix
// list. `passes`/`matches` are added, nothing is removed.
"unicorn/consistent-boolean-name": [
"error",
{ prefixes: { matches: true, passes: true } },
],
// Kebab-case files exporting PascalCase components: house convention.
"unicorn/filename-case": ["error", { case: "kebabCase" }],
// Keep the rule, drop one replacement: `props` is React's own word.
// `interface FilterBarProps` is the convention every React codebase and
// every reader expects; `FilterBarProperties` would be unidiomatic in
// service of a generic dictionary. Every other replacement stays on.
"unicorn/name-replacements": ["error", { replacements: { props: false } }],
},
},
];