Skip to content

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.com

Then install the package:

sh
pnpm add -D @studnicky/eslint-config

Install peer dependencies:

sh
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@>=6

Public 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(...).

SuiteDomain
entityModelSuiteType, 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
moduleDesignSuiteExport shape and method-body conventions — explicit-return-binding, export-shape, inline-trivial-logic, no-function-registries, require-options-object
diagnosticsSuiteDiagnostic suppression — clean-diagnostics
VocabularySuiteIdentifier 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.
classMechanicsSuiteClass-mechanics conventions — lexical-this-only, hash-private-fields, direct-invocation-only
LayerBoundarySuiteHexagonal-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.
v8ObjectShapeSuiteConstructs that destabilize V8 hidden-class/inline-cache assumptions — see the V8 rules table
v8CollectionTraversalSuiteHow a collection is walked, not how often — see the V8 rules table
v8RepeatedWorkSuiteCost 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.

js
// 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:

js
// 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:

js
// 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:

ts
// 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.

RuleFixableSeverity
@studnicky/adapter-only-importNoerror
@studnicky/all-types-are-entitiesNoerror
@studnicky/clean-diagnosticsYeserror
@studnicky/descriptive-identifiersNoerror
@studnicky/direct-invocation-onlyNoerror
@studnicky/domain-purityNoerror
@studnicky/entity-file-shapeNoerror
@studnicky/explicit-return-bindingNoerror
@studnicky/export-shapeNoerror
@studnicky/hash-private-fieldsNoerror
@studnicky/inline-trivial-logicYeserror
@studnicky/intake-parse-onlyNoerror
@studnicky/interface-must-be-contractYeserror
@studnicky/interfaces-compose-named-typesNoerror
@studnicky/known-types-outside-adaptersNoerror
@studnicky/layer-import-boundaryNoerror
@studnicky/lexical-this-onlyNoerror
@studnicky/no-circular-importsNoerror
@studnicky/no-double-assertionNoerror
@studnicky/no-function-registriesNoerror
@studnicky/no-mixed-callable-shapesNoerror
@studnicky/no-native-errorNoerror
@studnicky/no-caller-chosen-guard-typeNoerror
@studnicky/no-redefined-external-typesNoerror
@studnicky/no-reflect-argument-launderingNoerror
@studnicky/no-threaded-vocabularyNoerror
@studnicky/no-unchecked-overload-implementationNoerror
@studnicky/no-unparsed-assertionNoerror
@studnicky/prefer-collection-typesNowarn
@studnicky/require-options-objectNoerror
@studnicky/static-method-verbsNoerror
@studnicky/type-alias-invariantsPartialerror

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.

RuleFixableSeverity
@studnicky/v8/arguments-objectNoerror
@studnicky/v8/computed-class-propertiesNoerror
@studnicky/v8/computed-object-propertiesNoerror
@studnicky/v8/conditional-property-assignmentNoerror
@studnicky/v8/define-propertyNoerror
@studnicky/v8/delete-propertyNoerror
@studnicky/v8/dynamic-property-accessNoerror
@studnicky/v8/eval-functionNoerror
@studnicky/v8/object-spreadNoerror
@studnicky/v8/prototype-modificationNoerror
@studnicky/v8/with-statementNoerror

Collection traversal — v8CollectionTraversalSuite ​

How a collection is walked, not how often.

RuleFixableSeverity
@studnicky/v8/array-from-iteratorsNoerror
@studnicky/v8/array-from-map-callbackNoerror
@studnicky/v8/chained-array-iterationNoerror
@studnicky/v8/for-in-loopsNoerror
@studnicky/v8/for-of-arraysNoerror

Repeated work — v8RepeatedWorkSuite ​

Cost that compounds per hot-loop iteration.

RuleFixableSeverity
@studnicky/v8/array-concat-outside-loopsNoerror
@studnicky/v8/array-scan-outside-loopsNoerror
@studnicky/v8/array-splice-outside-loopsNoerror
@studnicky/v8/array-spread-outside-loopsNoerror
@studnicky/v8/inline-arrow-functionsNoerror
@studnicky/v8/inline-functionsNoerror
@studnicky/v8/max-switch-casesNoerror
@studnicky/v8/memoize-array-lengthNoerror
@studnicky/v8/regexp-in-loopsNoerror
@studnicky/v8/switch-statementsNoerror
@studnicky/v8/try-catch-in-loopsNoerror