mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 12:07:48 +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
60 lines
2.1 KiB
JavaScript
60 lines
2.1 KiB
JavaScript
// Test files: the rules that catch a test which cannot fail, plus the
|
|
// relaxations that stop the strict presets fighting mocks and fixtures.
|
|
//
|
|
// `expect-expect` is the article's "tests with no assertions" -- a model
|
|
// writes `it("works", () => { render(<X/>) })` and it passes forever.
|
|
// `no-focused-tests` is the forgotten `.only` that silently skips the suite.
|
|
import vitest from "@vitest/eslint-plugin";
|
|
|
|
/** Globs vitest rules apply to. */
|
|
export const TEST_FILES = [
|
|
"**/*.test.{ts,tsx,js,jsx}",
|
|
"**/*.spec.{ts,tsx,js,jsx}",
|
|
"**/test/**",
|
|
"**/__tests__/**",
|
|
"**/__fixtures__/**",
|
|
];
|
|
|
|
const RULES = {
|
|
...vitest.configs.recommended.rules,
|
|
"vitest/expect-expect": "error",
|
|
"vitest/no-disabled-tests": "error",
|
|
"vitest/no-focused-tests": "error",
|
|
// vitest's `expect(value, message)` is how a loop of assertions names the
|
|
// case that failed; the rule's default of one argument would ban it.
|
|
"vitest/valid-expect": ["error", { maxArgs: 2 }],
|
|
};
|
|
|
|
/** @type {import("eslint").Linter.Config[]} */
|
|
export const vitestConfig = [
|
|
{
|
|
files: TEST_FILES,
|
|
plugins: { vitest },
|
|
rules: RULES,
|
|
},
|
|
// `typecheck` makes valid-title & co. read the TS type of a test's
|
|
// arguments; it needs parser services, which only the TS files have.
|
|
{
|
|
files: ["**/*.{ts,tsx,mts,cts}"],
|
|
settings: { vitest: { typecheck: true } },
|
|
},
|
|
{
|
|
files: TEST_FILES,
|
|
rules: {
|
|
"@typescript-eslint/no-empty-function": "off",
|
|
// Tests reach for `!` on queries that cannot return null in a passing
|
|
// test; asserting them again would only add noise.
|
|
"@typescript-eslint/no-non-null-assertion": "off",
|
|
"@typescript-eslint/require-await": "off",
|
|
"@typescript-eslint/unbound-method": "off",
|
|
"sonarjs/no-duplicate-string": "off",
|
|
// Test helpers live beside the tests that use them; hoisting every
|
|
// fixture factory to module scope to satisfy a scoping rule would
|
|
// scatter each test's setup away from the test.
|
|
"unicorn/consistent-function-scoping": "off",
|
|
// A no-op stub is the point of a stub.
|
|
"unicorn/no-empty-file": "off",
|
|
},
|
|
},
|
|
];
|