{"_id":"@deniszhitnyakov/eslint-plugin-hbo","_rev":"9-c9716e926209159c8c4e4fca7c33cc13","name":"@deniszhitnyakov/eslint-plugin-hbo","dist-tags":{"latest":"0.7.0"},"versions":{"0.1.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.1.0","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.1.0","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"e2c165040e42797aba7fabc0e23b7e55c47b128f","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.1.0.tgz","fileCount":223,"integrity":"sha512-/XYWKufMzzJX1CP9tL2D9Im6RmkFwZEcSEQyLFz9q0k0rivdQMbLerZEhzKuJu9zaZUnLNJMeLv9qrtukYU6rQ==","signatures":[{"sig":"MEUCIQCeNcJxTjqSoJ50noRF41obEMXYGSEzvddSU4YyfBUYbAIgNitEU7HGcRF+joNtVp8B4heb23WpsCAfmvJ2jx32gVg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":327208},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"1d996e8cacdc65f5ed94867cb220b2927765748c","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.1.0_1779278568772_0.21675623563958557","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.2.0","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.2.0","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"58ce15fcfbd71be91db12b34a96004376ee2bc9f","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.2.0.tgz","fileCount":223,"integrity":"sha512-xkUElyvQJmkdXD154gopZMQmI5NB7MOKH40DfkoCYR1YX6YSWDRhPqtMlUW17O3CUpSy7FrvjEHki0sQx6bI7A==","signatures":[{"sig":"MEQCIEK5a3E4aaAApyR4G0SC5iTf9pQxirkqytdj8sYb4j9GAiAn1Knq/riV0XD8WRSJ0sz7Z1Yk/kjMmpByKVsirv1tzQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":327208},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"333a03d0b7e6d023c3af38216aa8857be5443a22","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.2.0_1779278848740_0.17710429231341496","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.3.0","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.3.0","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"bea31c55c92851f52c6eed7dc6233209b6b6c05c","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.3.0.tgz","fileCount":243,"integrity":"sha512-TI4f1vjrONZKdcV2t7shRuOtxBQfjXGHIlD/ZFwvgm1CywNU+CYccZv+jrTP4e3vH9eJYcub+qvNwXAfFN3dqQ==","signatures":[{"sig":"MEYCIQCqYzmstd3oSCdTkJMTWvW2jnCDrWtK60Hg/AhnkIgGrwIhAKz1DJnIFBjX22G38oKCJna/n48RwQ6qVJDF1OpV2GPk","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":345184},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"333a03d0b7e6d023c3af38216aa8857be5443a22","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.3.0_1779565839309_0.3250199089257235","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.4.1","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.4.1","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"bf3f0ecf52952fd8aee4a417f3b410fc3194d7b4","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.4.1.tgz","fileCount":251,"integrity":"sha512-1v5nlvFm1PF+Syob2ViOw64YfJWbdkXggX9axHgMTmswQc0OwsvNhQFbgb0SG038/BXxM/K5igrfr6Aln/27Gg==","signatures":[{"sig":"MEUCIQDftPsDtqx0mHmXvs04IK3Axbfl6XTRT7s6ZLuRqrhZzwIgaWX1A7NOd8c0AZM/2LEUNfEF1onejMxvFq/DJPxI/fE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":358526},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"4558d6de4da10557e34004d9a80b82412a899a28","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.4.1_1779570823826_0.4471324969377142","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.5.0","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.5.0","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"89ee68b42cb6b4a3937635c242199498301d7a45","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.5.0.tgz","fileCount":259,"integrity":"sha512-y/2p2x2Z/35tg93r6zijzemuGM5UYJHpPuVh1AMi7lm9Xu2oI+QsH8PpvDvTpXnrhgahvdIrABa+gJwbsITb4g==","signatures":[{"sig":"MEQCIGMDSYRpPu8LYjWF7nOUogGPNLKDasub6FV+vYin7MxcAiA2i3efSfmxPYmitRAXfK39CsVPn3/5MiJpL8COni/Q/w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":387936},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"8cd9ef7f8c98dbb53d3a84a29dc0b405c488cc56","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.5.0_1779714317914_0.5094918910406325","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.6.0","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"url":"https://github.com/ztnkv","name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com"},"license":"MIT","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.6.0","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"dist":{"shasum":"76220e6a603be8ee922bc6fc91af2306117bbe79","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.6.0.tgz","fileCount":267,"integrity":"sha512-wTTFDSwzLNLBJICsxxuasHdx1ufNvvMDA8x1yVXCQIraR62kVwMCCXkvHy6evAUqPPT9hFge3cefCn1GmLJfNA==","signatures":[{"sig":"MEUCIAx3EwQceMl0WWPmQkk0XYl09knwFqpRNhABcUVHup88AiEAw3m2uEcydYaI04M0nZ2XMSQd/Ysl3veLmCZZsbOGU6E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":416406},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=20"},"gitHead":"8cd9ef7f8c98dbb53d3a84a29dc0b405c488cc56","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"repository":{"url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git","type":"git"},"_npmVersion":"11.12.1","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@typescript-eslint/utils":"^8.59.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","typescript":"^5.7.0","@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3"},"peerDependencies":{"eslint":">=9.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-hbo_0.6.0_1779744523862_0.8775618421407025","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@deniszhitnyakov/eslint-plugin-hbo","version":"0.7.0","description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"author":{"name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com","url":"https://github.com/ztnkv"},"license":"MIT","homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","repository":{"type":"git","url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git"},"bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"type":"module","main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","test":"vitest run","prepublishOnly":"npm run build"},"peerDependencies":{"eslint":">=9.0.0"},"dependencies":{"@typescript-eslint/utils":"^8.59.3"},"devDependencies":{"@types/node":"^22.10.0","@typescript-eslint/rule-tester":"^8.59.3","typescript":"^5.7.0","vitest":"^3.0.0"},"engines":{"node":">=20"},"gitHead":"35910d5c79fd9c2ad747896f19c6942775f191bd","_id":"@deniszhitnyakov/eslint-plugin-hbo@0.7.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-kmyl/axefiHl36+8IgqF7jfT2A7OCrMpncsi9jYSNtBJ5w4ontPo8A19p8xX1DQCUb3FSMQSmCV4XuVQEYg5vA==","shasum":"81de7af8a6d663392a4d18d1f2ca5e0ae740a9c9","tarball":"https://registry.npmjs.org/@deniszhitnyakov/eslint-plugin-hbo/-/eslint-plugin-hbo-0.7.0.tgz","fileCount":275,"unpackedSize":429421,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBeCR5PCcMow5P9wYywBYyGV3MEBKtYX5/fxtGzpOPIwAiA6yUundDOJOvODd3nWt4j5Uwt/TutYojyciazku9vNVA=="}]},"_npmUser":{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"},"directories":{},"maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-hbo_0.7.0_1779796403147_0.48657713211847176"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T12:02:48.603Z","modified":"2026-05-26T11:53:23.412Z","0.1.0":"2026-05-20T12:02:48.941Z","0.2.0":"2026-05-20T12:07:28.937Z","0.3.0":"2026-05-23T19:50:39.481Z","0.4.0":"2026-05-23T20:42:34.119Z","0.4.1":"2026-05-23T21:13:43.984Z","0.5.0":"2026-05-25T13:05:18.083Z","0.6.0":"2026-05-25T21:28:44.023Z","0.7.0":"2026-05-26T11:53:23.295Z"},"bugs":{"url":"https://github.com/ztnkv/eslint-plugin-hbo/issues"},"author":{"name":"Denis Zhitnyakov","email":"denis.zhitnyakov@gmail.com","url":"https://github.com/ztnkv"},"license":"MIT","homepage":"https://github.com/ztnkv/eslint-plugin-hbo#readme","keywords":["eslint","eslintplugin","eslint-plugin","typescript","code-style","testing"],"repository":{"type":"git","url":"git+https://github.com/ztnkv/eslint-plugin-hbo.git"},"description":"Opinionated ESLint rules — author's own conventions: blank-line discipline, error-handling, test file layout, naming hygiene.","maintainers":[{"name":"deniszhitnyakov","email":"hutches.bath.2y@icloud.com"}],"readme":"# @deniszhitnyakov/eslint-plugin-hbo\n\nOpinionated ESLint rules. My own set of conventions, distilled from the code I\nwrite across projects — blank-line discipline, error-handling boundaries, test\nfile layout, naming hygiene.\n\nI'm publishing them as a plugin because I want the same rules in every\ncodebase I touch. You're welcome to use them, but be warned: this is not a\nneutral linter — it's a personal style guide expressed in AST checks. If you\ndisagree with a rule, don't enable it.\n\nThe \"hbo\" suffix is a brand, not a project lock-in. The rules are\nproject-agnostic; a handful that previously hardcoded project-specific names\n(base error class, test suffixes) now expose them as options with my own\ndefaults.\n\n## Install\n\n```sh\nnpm install --save-dev @deniszhitnyakov/eslint-plugin-hbo\n```\n\nPeer dependency: `eslint >= 9`.\n\n## Usage\n\n```js\n// eslint.config.js\nimport hbo from '@deniszhitnyakov/eslint-plugin-hbo';\n\nexport default [\n  {\n    plugins: { hbo },\n    rules: {\n      'hbo/blank-line-after-guard-return': 'error',\n      'hbo/no-throw-standard-error': ['error', { baseClass: 'AppError' }],\n      'hbo/test-file-suffix-allowlist': [\n        'error',\n        { allowedSuffixes: ['.unit.test.ts', '.integration.test.ts'] },\n      ],\n      // ...\n    },\n  },\n];\n```\n\n## Rules\n\n### Blank-line discipline\n\n| Rule | What it enforces |\n|---|---|\n| `blank-line-after-guard-continue` | Blank line after a guard `continue` inside a loop. |\n| `blank-line-after-guard-return` | Blank line after a guard `return` at the top of a function. |\n| `blank-line-before-final-return` | Blank line before the final `return` of a function (unless it's the only statement). |\n| `block-do-while-blank-lines` | Blank lines around `do...while` blocks. |\n| `block-for-blank-lines` | Blank lines around `for` blocks. |\n| `block-if-blank-lines` | Blank lines around `if` blocks. |\n| `block-switch-blank-lines` | Blank lines around `switch` blocks. |\n| `block-while-blank-lines` | Blank lines around `while` blocks. |\n| `object-literal-property-spacing` | Blank-line separation between properties of an object literal. |\n\n### Error handling\n\n| Rule | What it enforces | Options |\n|---|---|---|\n| `no-throw-standard-error` | Disallows `throw new Error / TypeError / RangeError / ...`. All throws must extend the project base error class. | `baseClass: string` — defaults to `AppError`. |\n\n### Test file layout\n\n| Rule | What it enforces | Options |\n|---|---|---|\n| `test-file-suffix-allowlist` | Files inside `**/__tests__/` must end with one of the allowed suffixes. Exactly one recognized style token (`detroit`, `london`, `contract`, `security`) may precede `.test.ts(x)` — compound names like `.security.detroit.test.ts` are rejected. | `allowedSuffixes: string[]` — defaults to `.detroit.test.ts(x)`, `.london.test.ts(x)`, `.contract.test.ts(x)`, `.security.test.ts(x)`. |\n| `no-test-file-name-mismatch` | For `<base>.<style>.test.ts` inside `__tests__/`, a matching `<base>.ts` must exist in the parent area folder. `.security.test.ts(x)` is exempt: a security test names an invariant and is valid with or without a production sibling. | `testSuffixes: string[]` (suffixes that require a sibling) — defaults to `.detroit.test.ts`, `.london.test.ts`, `.contract.test.ts`. `exemptSuffixes: string[]` — defaults to `.security.test.ts`, `.security.test.tsx`. |\n| `test-body-aaa-blank-lines` | A test body must be split into Arrange / Act / Assert by exactly two blank-line separators between top-level statements. | — |\n| `single-expect-per-test` | One `expect(...)` per `it()` / `test()`. | — |\n| `test-matcher-style` | Enforces a single matcher style per assertion (e.g. always `.toBe(true)` not `.toBeTruthy()` for booleans). | — |\n| `expect-blank-line` | A blank line before every top-level `expect(...)` inside a test body (the Act/Assert seam). | — |\n\n### Test helpers placement\n\n| Rule | What it enforces |\n|---|---|\n| `no-inline-test-factories` | Factories (`make*`, `create*`, `build*`, `arrange*`, `Fake*`, `Mock*`) used inside test files must live in `__test-support__/`. |\n| `no-reusable-inline-test-helpers` | Any top-level helper used in 2+ test blocks must live in `__test-support__/`. |\n\n### Testing selectors\n\n| Rule | What it enforces | Options |\n|---|---|---|\n| `no-non-testid-queries` | Single elements must be selected by the test-id attribute only. Flags Testing Library / Playwright `getBy*`/`queryBy*`/`findBy*` (everything but the `TestId` suffix), CSS string selectors in `querySelector`/`closest`/Playwright `locator`/`$`/Cypress `cy.get` that don't reduce to `[data-testid…]`, native `getElementById`, and `cy.contains` (text selection). `get`/`contains` only fire on a `cy` chain root, so `Map.get` is safe; bare `.find()` is left alone. | `attribute: string` (default `data-testid`), `includeMultiple: boolean` (default `false`; also flags `getAllBy*`, `querySelectorAll`, `$$`, `getElementsBy*`), `allowDynamicSelectors: boolean` (default `true`; skips non-static selector args), `allowedMethods: string[]` (default `[]`; escape hatch by method name). |\n\n### Naming & structure\n\n| Rule | What it enforces |\n|---|---|\n| `identifier-denylist` | Hard ban on a configurable list of identifier names (e.g. `data`, `info`, `manager`, `handler`). |\n| `no-multiple-classes-per-file` | One top-level class per file. |\n| `no-standalone-functions-with-class` | A file with a top-level class cannot also export standalone top-level functions. |\n\n### Comments\n\n| Rule | What it enforces |\n|---|---|\n| `no-multiline-comments` | No multi-line `/* ... */` comments; no consecutive `//` lines forming a paragraph. |\n| `no-code-comments` | No code comments. The only permitted forms: local next-line rule disables (`// eslint-disable-next-line ...`, `// @ts-expect-error ...`); todo-tags (`// TODO:`, `// FIXME:`, `// FIX:`, `// HACK:`, `// NOTE:`, `// XXX:`, `// BUG:` — also the `TAG(owner): ...` form); and the emergency-escape `// ! <reason>` for cases where a comment is genuinely unavoidable. |\n\n### Database migrations\n\n| Rule | What it enforces | Options |\n|---|---|---|\n| `migration-must-have-test` | Every forward-only `*.sql` migration in a `migrations/` dir must have a co-located verification test `__tests__/<base>.test.ts` (Testcontainers smoke + schema assertion). Skips the drizzle `meta/` folder and the tests themselves. | `migrationsDir` (default `migrations`), `testsDir` (default `__tests__`), `testSuffix` (default `.test.ts`; the `.test.tsx` twin is also accepted). |\n\n`.sql` files aren't in ESLint's default lint set, so this rule keys off the\nfilename via a tiny passthrough parser the plugin ships (`hbo.parsers.passthrough`).\nWire it up with a dedicated flat-config block scoped to your migrations:\n\n```js\n// eslint.config.js\nimport hbo from '@deniszhitnyakov/eslint-plugin-hbo';\n\nexport default [\n  {\n    files: ['**/migrations/**/*.sql'],\n    languageOptions: { parser: hbo.parsers.passthrough },\n    plugins: { hbo },\n    rules: { 'hbo/migration-must-have-test': 'error' },\n  },\n];\n```\n\n### Framework-specific\n\n| Rule | What it enforces |\n|---|---|\n| `no-empty-array-zustand-selector` | Catches a common Zustand foot-gun — selectors that return a fresh `[]` on every call and trigger re-renders. |\n\n### Internationalization (i18n)\n\n| Rule | What it enforces | Options |\n|---|---|---|\n| `no-hardcoded-jsx-literals` | An i18n-aware gate for codebases whose contract is \"in JSX, only translation keys — no hardcoded copy\". Flags human-language `JSXText` and bare string/template-literal children; leaves already-localized markup (`{t('key')}`, `<Trans>…</Trans>`) and non-language content (numbers, punctuation, single glyphs) alone. | `translationFunctionNames: string[]` (default `['t']`), `translationComponentNames: string[]` (default `['Trans']`), `allowedStrings: string[]` (default `[]`), `checkAttributes: boolean` (default `false`), `attributes: string[]` (default `['aria-label', 'title', 'placeholder', 'alt', 'label']`), `minLetters: number` (default `2`). |\n\nThis is not a clone of `react/jsx-no-literals` — that rule flags *every* literal\nand has no idea what `t(...)` or `<Trans>` mean, so it can't tell user-facing\ncopy from technical prop enums. `no-hardcoded-jsx-literals` is i18n-aware: it\nrecognizes your translation API and only flags copy that actually needs a key.\n\n```jsx\n// incorrect — hardcoded user-facing copy\n<Typography>Colour</Typography>\n<Box>{'Some copy'}</Box>\n<Box>{`Some copy`}</Box>\n\n// correct — localized, or not language\n<Typography>{t('palette.colour')}</Typography>\n<Trans i18nKey=\"palette.colour\">Colour</Trans>\n<Box>{count}</Box>\n<Stack direction=\"row\" variant=\"h5\" />   // technical props: attributes unchecked by default\n```\n\nScope the rule yourself — it ships no hardcoded file exclusions. Point it at your\ncomponent code and exclude stories / design-system / generated files via\n`files` / `ignores` in your flat config:\n\n```js\n// eslint.config.js\nimport hbo from '@deniszhitnyakov/eslint-plugin-hbo';\n\nexport default [\n  {\n    files: ['src/**/*.tsx'],\n    ignores: ['**/*.stories.tsx'],\n    plugins: { hbo },\n    languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } },\n    rules: {\n      // Start at 'warn' while you migrate strings to keys, then promote to 'error'.\n      'hbo/no-hardcoded-jsx-literals': [\n        'warn',\n        { translationFunctionNames: ['t'], translationComponentNames: ['Trans'] },\n      ],\n    },\n  },\n];\n```\n\nRecommended severity: start with `warn`. There is **no autofix** — replacing a\nliteral with `t('key')` means choosing a key, which is a human decision.\n\n## Philosophy\n\nA few principles behind these rules:\n\n- **Blank lines are syntax.** I treat blank-line discipline as a first-class part of the language. Visual rhythm is not cosmetic; it carries meaning about block boundaries and execution flow. Most of the rules above exist because I got tired of code reviews about it.\n- **Errors are first-class.** Throwing `new Error(\"...\")` from production code is a bug, not shorthand. The codebase should declare its error vocabulary as a class hierarchy, and the linter should enforce it.\n- **Tests announce their style.** Detroit, London, Contract — three different schools of testing with different rules about doubles and isolation. A test file's name tells you which one it follows; the linter ensures the rest of the file lives by that choice. `security` is a fourth, orthogonal category: a `*.security.test.ts` pins an invariant (authz-gating, read-only access, secrets redaction) so it's visible by name, runnable as a group, and red in CI when someone cuts the rail — and unlike a style test, it needs no production sibling.\n- **Helpers don't squat in tests.** A factory or helper used in 2+ tests is a public artifact and belongs in `__test-support__/`. Squatting it inline costs review time forever.\n- **Names follow a verb dictionary.** Top-level functions start with a verb from a small dictionary. No `data`, no `info`, no `manager`. Identifiers stay short, intent-revealing, one word per concept.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}