mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-13 22:11:00 +01:00
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
49 lines
1.8 KiB
JavaScript
49 lines
1.8 KiB
JavaScript
// React layer, stacked on base().
|
|
//
|
|
// Four plugins, each covering ground the others do not:
|
|
// - react-hooks: the rules of hooks + exhaustive deps, plus React 19's
|
|
// compiler-derived checks (`recommended` in 7.x).
|
|
// - @eslint-react: component/JSX correctness, type-checked variant, and the
|
|
// replacement for eslint-plugin-react (which crashes under ESLint 10).
|
|
// - react-refresh: a file that mixes a component with a non-component
|
|
// export silently breaks Vite HMR for the whole module.
|
|
// - react-you-might-not-need-an-effect: the article names "an effect that
|
|
// derives state from props" as the single most frequent AI edit. The
|
|
// fixture's Button does exactly that, and this is the rule that sees it.
|
|
// - jsx-a11y: accessibility is "the first thing to go" when a model
|
|
// optimises for how the screen looks. Its 6.10.2 peer range stops at
|
|
// ESLint ^9, but under 10 it runs cleanly (verified by the fixture test),
|
|
// so the `strict` preset is on.
|
|
import eslintReact from "@eslint-react/eslint-plugin";
|
|
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
import reactRefresh from "eslint-plugin-react-refresh";
|
|
import youMightNotNeedAnEffect from "eslint-plugin-react-you-might-not-need-an-effect";
|
|
|
|
import { TS_FILES } from "./base.js";
|
|
|
|
/** @type {import("eslint").Linter.Config[]} */
|
|
export const react = [
|
|
{
|
|
...eslintReact.configs["recommended-type-checked"],
|
|
files: TS_FILES,
|
|
},
|
|
{
|
|
files: TS_FILES,
|
|
plugins: { "react-hooks": reactHooks },
|
|
rules: reactHooks.configs.recommended.rules,
|
|
},
|
|
{
|
|
files: ["**/*.tsx", "**/*.jsx"],
|
|
...jsxA11y.flatConfigs.strict,
|
|
},
|
|
{
|
|
files: TS_FILES,
|
|
...youMightNotNeedAnEffect.configs.recommended,
|
|
},
|
|
{
|
|
files: ["**/*.tsx", "**/*.jsx"],
|
|
...reactRefresh.configs.vite,
|
|
},
|
|
];
|