ESLint preset
The authoring-lint preset shipped as `@homepages/eslint-plugin-template` — setup and scope. Per-rule reference lives in `rules/`.
@homepages/eslint-plugin-template is a flat-config ESLint preset carrying every
authoring rule the kit enforces one file at a time, under the template-kit/*
rule namespace.
npm i -D @homepages/eslint-plugin-template eslint @typescript-eslint/parserimport kit from "@homepages/eslint-plugin-template";export default kit;That is the whole setup — no plugin registration, no parser wiring, no tsconfig.
The preset registers its rules and sets languageOptions (parser included) itself,
so those two lines are a complete config.
The preset ships as its own package rather than a kit subpath because it needs a different dependency tree: ESLint and the parser, not React. That keeps a linter out of the production install of every app that renders a template — the kit’s main entry is a set of React primitives, and it must not drag one in.
ESLint and the parser are peer dependencies, so you install them yourself; the preset
resolves them from your workspace. If you see
Cannot find package '@typescript-eslint/parser', that is the install line above
missing.
Setup stays two lines, but the preset is not syntax-only: one entry, the
correctness tier, sets parserOptions.projectService and reads your workspace’s
tsconfig.json. That asks for nothing you do not already have — template-kit check
refuses to run without a tsconfig.json extending the kit’s — but it has one
consequence worth knowing: a .ts/.tsx file that your tsconfig.json does not
include is reported as a parsing error rather than being skipped, and no other rule
runs on it. Scaffolded workspaces include templates/**, which is where section code
lives; tooling config files (*.config.ts) are excluded from the preset already.
The authoring rules themselves stay syntactic, so they still fire on a file whose types cannot be resolved.
The preset is consumed once at a workspace root and lints every section
nested under templates/**; there are no per-template config files. It ships
three config entries, matched by glob rather than by directory depth:
| Entry | Files | Rules |
|---|---|---|
template-kit/authoring |
**/*.ts, **/*.tsx |
the broadly file-scoped rules — see rules/ |
template-kit/contract-files |
**/Renderer.tsx, **/section.ts, **/fixtures.ts |
no-client-directive-in-contract, no-node-builtins-in-contract |
template-kit/renderer |
**/Renderer.tsx |
props-from-schema |
template-kit/theme-source |
**/theme.ts, **/theme.test.ts |
turns no-hex off — the palette file is where a raw colour belongs |
template-kit/correctness |
**/*.ts, **/*.tsx |
the type-aware correctness tier, below |
The contract-files entry lists the three files a section’s contract lives in (Sections and declarations).
Server-vs-client scoping is decided per file, by the "use client" directive
prologue — see server vs client for what that means
and exactly which rules stop at that boundary.
The correctness tier
Section titled “The correctness tier”Eight rules from @typescript-eslint that are not about templates. They are here
because each one can put a wrong byte in the bundle a visitor downloads while
tsc --strict stays green:
| Rule | What it stops reaching the page |
|---|---|
no-base-to-string |
String(value) rendering the literal text [object Object] |
restrict-template-expressions |
the same defect inside `${value}` (numbers and booleans are allowed) |
restrict-plus-operands |
"1" + 1 rendering 11 |
no-misused-promises |
an async event handler passed where a void return is expected, whose rejection has nowhere to go |
no-floating-promises |
a promise nobody awaits or catches, failing silently in a published page |
await-thenable |
await on a non-promise, so the next line reads a value that is not ready |
no-for-in-array |
for...in over an array, yielding string indices and inherited keys |
no-implied-eval |
code passed as a string to a timer or constructed at runtime, which a real deployment’s CSP blocks |
These are the same rules a submitted template is judged by. That is deliberate: what gates a pack on the way in has to be something its author could already run.
House style — import order, naming conventions, banned identifiers — is not here and never will be. Your codebase’s conventions are yours.
Each rule is documented upstream by
typescript-eslint; they keep their own
@typescript-eslint/* ids in output, so a message resolves to that documentation rather
than to this corpus.
The rules
Section titled “The rules”Every rule the preset enforces — what it bans, why, and a before/after fix — is
documented once, per id, in rules/. Rules scoped to a single
file are enforced here, by this preset; rules scoped to the tree — a missing
contract file, a slot declared in the schema but unmarked in Renderer.tsx, a CSS
budget — cannot be expressed one-file-at-a-time and belong to
template-kit check under the same template-kit/<id> namespace. One
namespace, two venues: the id printed by either failure resolves to exactly one page.
See also
Section titled “See also”- Rule index — every rule id, with a page each, across both venues.
- Server vs client — how a file is scoped, and which rules stop at that boundary.
- Run
check— the tree-scoped half of the same rule namespace. - Versioning — what this package’s version number means, and why it always matches the kit’s.