Skip to content

Repository files navigation

eslint-plugin-ghost

Architecture-enforcing ESLint rules. The kind of thing you'd normally write a wiki page about and then watch everyone ignore — except these fail the build.

Each rule encodes one architectural decision that's expensive to relitigate in code review every week: keep wall-clock reads out of domain logic, never lose the original error in a re-throw, no hardcoded English in the render layer, no process.env reaching into a class method. Static analysis instead of tribal knowledge.

11 rules, 94 tests, zero false-positive tolerance. ESLint 9 flat-config native, TypeScript-aware (uses @typescript-eslint/utils).

Install

npm i -D eslint-plugin-ghost
// eslint.config.js (flat config, ESLint 9+)
import ghost from 'eslint-plugin-ghost';

export default [
  {
    files: ['**/*.{ts,tsx}'],
    plugins: { ghost },
    rules: { ...ghost.configs.recommended.rules },
  },
];

See eslint.config.example.js for picking rules individually.

Rules

Rule Recommended What it enforces
no-date-in-domain error No wall-clock reads (new Date(), Date.now()) in domain files — inject a clock so logic stays replayable and testable.
no-process-env-in-method error No process.env access inside a class member — read config at the module boundary, not deep in behavior.
no-platform-branch error No runtime platform branching (Platform.OS, isWeb) inside render functions — split the file per platform instead.
no-test-only error No focused test markers (.only, fit, fdescribe) in committed code — the classic silent green CI.
no-catch-rewrap warn A re-throw inside catch must reference the caught error (cause) — never sever the stack.
require-error-logged warn A swallowed catch must log the caught variable at warn/error — no silent catch {}.
no-hardcoded-string-in-jsx warn No hardcoded user-visible strings in JSX — route copy through i18n.
no-hardcoded-user-message warn Exception constructors must not receive raw English literals — keep user-facing copy out of throw sites.
no-locale-method-without-locale warn Intl constructors must receive an explicit locale — no implicit machine-locale formatting.
no-timezone-naive-date warn No .toISOString() in display-layer files — render in the user's zone, not UTC.
no-bare-api-fetch off No bare fetch() to internal API routes — go through the typed client so request/response stay contract-checked.

configs.recommended wires the severities in the column above. no-bare-api-fetch ships off by default because the "internal route" shape is project-specific — turn it on and adapt as needed.

Why these specific rules

They came out of a large event-sourced TypeScript monorepo where the same review comments kept recurring. Every rule here replaced a recurring human nit with a machine one:

  • Replayability (no-date-in-domain, no-process-env-in-method) — domain logic that reads ambient state can't be deterministically replayed. Inject it.
  • Error causality (no-catch-rewrap, require-error-logged) — the two ways teams lose stack traces: re-throwing without cause, and swallowing silently.
  • Localization integrity (no-hardcoded-string-in-jsx, no-hardcoded-user-message, no-locale-method-without-locale, no-timezone-naive-date) — user-facing copy and formatting belong behind a boundary, not sprinkled through components and throw sites.
  • Platform hygiene (no-platform-branch) — one reference implementation, thin per-platform shells; no if (platform) ternaries in shared render code.
  • CI honesty (no-test-only) — a focused test passes green while skipping everything else. Ban it at the lint layer.

Develop

npm install
npm test          # vitest, 94 tests via @typescript-eslint/rule-tester
npm run build     # emits dist/ (ESM + .d.ts)
npm run typecheck

License

MIT © ghostON3

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages