ESLint Plugins
@studnicky/eslint-config ships two custom ESLint plugins:
@studnicky— Structural and semantic rules that enforce the substrate codebase doctrine.@studnicky/v8— 27 rules for V8 optimization-sensitive code and the related constructs the codebase constrains consistently.
Register both plugins in your flat config to enable the rules.
Install
Add the GitHub Packages registry to .npmrc:
@studnicky:registry=https://npm.pkg.github.comThen install the package:
pnpm add -D @studnicky/eslint-configInstall peer dependencies:
pnpm add -D eslint@>=10 typescript-eslint@>=8 @typescript-eslint/eslint-plugin@>=8 @typescript-eslint/parser@>=8 @stylistic/eslint-plugin@>=5 eslint-plugin-import-x@>=4 eslint-plugin-perfectionist@>=5 eslint-plugin-regexp@>=3 eslint-plugin-unused-imports@>=4 typescript@>=6Public API
Use @studnicky/eslint-config/node for the ESLint plugins and flat-config suites. @studnicky/eslint-config/browser exports PlatformCallDefaults, the browser-safe platform-call policy data. The browser entry does not export plugins or suites because those rules execute the TypeScript compiler API.
Import ProjectHostInterface from @studnicky/eslint-config/interfaces when a Node-hosted project-aware rule needs project services.
Browser surface
Use @studnicky/eslint-config/browser when a browser application needs PlatformCallDefaults. Configure ESLint plugins and suites through the Node entrypoint.
Suites are opt-in
A suite is a flat-config entry bundling one domain's rules at error. Spreading a suite is a deliberate choice to adopt that whole domain; registering plugin alone enables nothing. A static suite is a plain config object; a factory suite needs project-specific options its member rules cannot default, and is created with .create(...).
| Suite | Domain |
|---|---|
entityModelSuite | Type, interface, entity-namespace, and keyed-collection conventions — all-types-are-entities, entity-file-shape, interface-must-be-contract, interfaces-compose-named-types, no-double-assertion, no-mixed-callable-shapes, no-redefined-external-types, no-unparsed-assertion, prefer-collection-types, type-alias-invariants |
moduleDesignSuite | Export shape and method-body conventions — explicit-return-binding, export-shape, inline-trivial-logic, no-function-registries, require-options-object |
diagnosticsSuite | Diagnostic suppression — clean-diagnostics |
VocabularySuite | Identifier and vocabulary conventions — descriptive-identifiers, static-method-verbs, no-threaded-vocabulary. A factory: call VocabularySuite.create(...) with a sourceRoot, since no-threaded-vocabulary has no default for it. |
classMechanicsSuite | Class-mechanics conventions — lexical-this-only, hash-private-fields, direct-invocation-only |
LayerBoundarySuite | Hexagonal-architecture and entity-intake boundaries — adapter-only-import, domain-purity, intake-parse-only, known-types-outside-adapters, layer-import-boundary, no-circular-imports, no-reflect-argument-laundering, no-unchecked-overload-implementation. A factory, not a static config: call LayerBoundarySuite.create(...) with the shared layer config, since four of the eight rules take distinct extra options on top of a common layers/sourceRoot shape, intake-parse-only takes its own unrelated options, and the remaining three take none. |
v8ObjectShapeSuite | Constructs that destabilize V8 hidden-class/inline-cache assumptions — see the V8 rules table |
v8CollectionTraversalSuite | How a collection is walked, not how often — see the V8 rules table |
v8RepeatedWorkSuite | Cost that compounds per hot-loop iteration — see the V8 rules table |
Enable individual rules instead when a domain's conventions do not apply. type-alias-invariants governs how a type alias establishes schema provenance and stands on its own; all-types-are-entities additionally requires every canonical alias to be the exported Type member of an *Entity namespace, which is a convention a consumer adopts by enabling entityModelSuite, not a prerequisite for the other rules.
// eslint.config.mjs — one rule, without the entity conventions
import { plugin } from "@studnicky/eslint-config/node";
export default [
{
plugins: { "@studnicky": plugin },
rules: { "@studnicky/type-alias-invariants": "error" },
},
];Usage
Import plugin and v8Plugin and register them in a flat-config entry:
// eslint.config.mjs
import { plugin, v8Plugin } from "@studnicky/eslint-config/node";
export default [
{
plugins: { "@studnicky": plugin, "@studnicky/v8": v8Plugin },
rules: {
"@studnicky/type-alias-invariants": "error",
"@studnicky/v8/array-spread-outside-loops": "error",
},
},
];Combine with additional rules in the same entry:
// eslint.config.mjs
import { plugin, v8Plugin } from "@studnicky/eslint-config/node";
export default [
{
plugins: { "@studnicky": plugin, "@studnicky/v8": v8Plugin },
rules: {
"@studnicky/type-alias-invariants": "error",
"@studnicky/v8/array-spread-outside-loops": "error",
"no-console": "warn",
},
},
];Using the plugins directly
Import the raw plugin objects for hand-rolled flat config:
// eslint.config.ts
import { plugin, v8Plugin } from "@studnicky/eslint-config/node";
export default [
{
plugins: {
"@studnicky": plugin,
"@studnicky/v8": v8Plugin,
},
rules: {
"@studnicky/export-shape": "error",
"@studnicky/v8/delete-property": "error",
},
},
];Configuration rules
32 rules that enforce structural, semantic, and stylistic constraints.
V8 rules
27 rules covering V8 optimization-sensitive allocation, object-shape, iteration, and dynamic-code patterns, alongside related source constraints where measurement does not establish a V8 cost. Grouped into three suites by what each rule protects against.
Object shape — v8ObjectShapeSuite
Constructs that destabilize V8 hidden-class and inline-cache assumptions.
| Rule | Fixable | Severity |
|---|---|---|
@studnicky/v8/arguments-object | No | error |
@studnicky/v8/computed-class-properties | No | error |
@studnicky/v8/computed-object-properties | No | error |
@studnicky/v8/conditional-property-assignment | No | error |
@studnicky/v8/define-property | No | error |
@studnicky/v8/delete-property | No | error |
@studnicky/v8/dynamic-property-access | No | error |
@studnicky/v8/eval-function | No | error |
@studnicky/v8/object-spread | No | error |
@studnicky/v8/prototype-modification | No | error |
@studnicky/v8/with-statement | No | error |
Collection traversal — v8CollectionTraversalSuite
How a collection is walked, not how often.
| Rule | Fixable | Severity |
|---|---|---|
@studnicky/v8/array-from-iterators | No | error |
@studnicky/v8/array-from-map-callback | No | error |
@studnicky/v8/chained-array-iteration | No | error |
@studnicky/v8/for-in-loops | No | error |
@studnicky/v8/for-of-arrays | No | error |
Repeated work — v8RepeatedWorkSuite
Cost that compounds per hot-loop iteration.
| Rule | Fixable | Severity |
|---|---|---|
@studnicky/v8/array-concat-outside-loops | No | error |
@studnicky/v8/array-scan-outside-loops | No | error |
@studnicky/v8/array-splice-outside-loops | No | error |
@studnicky/v8/array-spread-outside-loops | No | error |
@studnicky/v8/inline-arrow-functions | No | error |
@studnicky/v8/inline-functions | No | error |
@studnicky/v8/max-switch-cases | No | error |
@studnicky/v8/memoize-array-length | No | error |
@studnicky/v8/regexp-in-loops | No | error |
@studnicky/v8/switch-statements | No | error |
@studnicky/v8/try-catch-in-loops | No | error |