Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Linting

Rules for the mistakes the type checker cannot catch. They run as a plugin for ESLint and Oxlint, and as a standalone command.

pnpm add -D oxc-parser   # valof does not install it for you

Rules

rulereportsdefault
unused-membera member registered with .impl or .implTrait that nothing readswarning
duplicate-branda brand string claimed by more than one top-level aliaserror
brand-mismatcha brand whose last segment is not the name of the type it brandserror
unnecessary-aliasa type alias that is a second name for a Val, a Trait or an Enumerror
unimplemented-traita Trait a Val or an Enum declares that its companion does not implementerror
companion-mismatcha companion, or a variant, bound to a name other than its ownerror
detached-implan impl step written outside the chain that declares its companionerror
split-companiona companion for a type that another file declareserror
bypassed-companiona Val.of for a type whose companion is how it is builtwarning
unnamed-ofa Val.of that names no type, taking one from its targetwarning
incomplete-disablea disable comment leaving out the rules it silences, or its scopeerror
unused-disablea disable comment naming a rule that reports nothing therewarning

A rule warns where the code around the finding still works, and errors where a Val is broken: two types the checker stops distinguishing, or a name that has to agree with another and does not. incomplete-disable errors for a reason of its own. A comment naming no rule silences every one, a rule added next year included.

The severity is what the plugin sets, and a project can give any rule its own. The command prints every finding the same way and exits 1 on any of them.

A member registered with .impl({…}) is not tree-shaken, and knip does not report it when it becomes unused.

Disable comments

Silence one line with a comment above it,

// valof-lint-disable-next-line unused-member -- public API
shout: (u) => u.toUpperCase(),

or a whole file with one anywhere in it:

// valof-lint-disable-whole-file unused-member -- every export here is public API
// valof-lint-disable-all-whole-file -- generated, do not lint

Name the rules it silences, separated by a space or a comma. The comments follow two rules of their own, incomplete-disable and unused-disable, which are left out of a run and given a severity like any other.

Plugin for ESLint and Oxlint

The same rules, one per finding kind. Name one to give it its own severity, or turn it off. The project to read is one setting for all of them, a path or a list of them, and defaults to src/**/*.ts. A path starting with ! is excluded from it.

ESLint needs a parser that reads your TypeScript.

// eslint.config.js
import valof from "valof/eslint-plugin";

export default [
  {
    plugins: { valof },
    rules: { ...valof.configs.recommended.rules, "valof/unused-member": "off" },
    settings: { valof: { project: ["src/**/*.ts", "!src/generated/**"] } },
  },
];

Oxlint takes the same plugin, through its JS plugins.

// oxlint.config.ts
import { defineConfig } from "oxlint";
import valof from "valof/eslint-plugin";

export default defineConfig({
  jsPlugins: ["valof/eslint-plugin"],
  extends: [valof.configs.recommended],
  rules: { "valof/unused-member": "off" },
  settings: { valof: { project: "src" } },
});

CLI

pnpm exec valof-lint src                                # the whole project
pnpm exec valof-lint src src/billing/id.ts              # report on the changed file
pnpm exec valof-lint 'src/**/*.ts' '!src/generated/**'  # leave a generated tree out
argumentwhat it is
the first paththe project to read, a directory or a glob
the paths after itthe files to report on, the whole project when there are none
--project, --report-onthe same two by name, in either order. Either can be repeated, and takes !path
--no-<rule>a rule to leave out of the run, by the rule name in the finding

A single file given as the project is refused, because a duplicate brand needs the other alias to be seen.

A !path is excluded wherever it is written, and is removed from the run rather than only from the report, so what a generated tree declares no longer applies to the rest.