Lint Setup
Two steps. Five minutes.
1. Install
npm install -D ttsc @ttsc/lint typescript2. Write lint.config.ts
Drop a lint.config.ts next to tsconfig.json:
// lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint";
export default {
rules: {
"no-var": "error",
"prefer-const": "error",
"eqeqeq": "error",
"typescript/no-explicit-any": "warning",
},
format: {
printWidth: 100,
singleQuote: true,
trailingComma: "all",
},
} satisfies ITtscLintConfig;"error" fails the build. "warning" prints. "off" disables the rule.
Rules with options use an ESLint-style tuple such as ["error", { "allowElseIf": false }]. A built-in rule documented without options accepts only a severity, either bare or in a one-element tuple; adding an object or positional payload is a configuration error instead of a silently ignored setting, including while the rule is "off".
Use one spelling per rule in each rules object. Names that normalize to the same built-in rule are rejected together, even if their severities and options agree. This prevents two aliases from assigning different severity and option payloads to one rule. Separate flat-config entries may still override one another in their declared order.
To exclude generated files from linting entirely, add a top-level ignores list (e.g. ignores: [".next/**/*.ts", "next-env.d.ts"]). Without a files filter it is a global ignore: the matched paths are skipped by every rule, including rules inherited through extends.
On a case-insensitive volume, files and ignores globs also match the spelling the directory tree stores when the Program reaches a file through an import with different letter case.
Check, fix, format, and editor sessions relate project-local files to the directory where the TypeScript Program was opened (--cwd when supplied). Reaching that directory through a linked shell PWD does not change whether a Program file belongs to the project.
On Windows, containment also honors the filesystem’s actual directory identity. A distinct case-sensitive sibling is outside the project even when its path differs only in letter case.
The format block configures the formatter, keys mirror .prettierrc. Presence of the block (even empty format: {}) enables ttsc format at Prettier defaults. It does not make ttsc check fail on formatting unless you set format.severity.
Executable lint configs are evaluated in an isolated process and cached with their complete local module-resolution graph. The cache fingerprints imported files, package manifests, resolution directories, and package files without publishing package contents as recursively watched project data. Editing a helper, changing a package entry point, adding a higher-priority extension candidate, or replacing a symlink therefore invalidates the prior result without touching the config itself. TTSC_LINT_DISABLE_CONFIG_CACHE=1 remains available when diagnosing evaluation behavior; it is not required for imported helpers. Config and contributor logs are preserved on stderr so they cannot corrupt JSON CLI output or LSP frames.
On Windows, the TypeScript lint config’s descriptor evaluator needs scratch space on the drive named by the config path. If the system temporary directory resolves to another drive or overlaps the project or config directory, it first tries the already opened ttsc cache’s identities directory. During descriptor loading, a temporary directory above the project also yields to an existing cache because that ancestor can contain resolution inputs. Independent config evaluation may use a system temporary directory that is a strict external ancestor of the project or config directory. The evaluator can also use the nearest node_modules/.cache or an existing user temporary directory on the config’s drive. It uses the config directory itself only when that directory resolves to the same drive; otherwise it asks you to set TEMP, TMP, or TMPDIR to an existing directory there. The evaluator removes its temporary files when it finishes.
An executable config is a Node module, and it runs under the module format Node would give it: an explicit .cts/.mts extension decides on its own, and an ambiguous .ts or .js config follows the nearest package.json "type" above it. So __dirname works in a CommonJS package and import.meta.dirname works in a "type": "module" package, each without renaming the file. The config’s own Program also sees every @types package installed beside it, so Node’s globals need no /// <reference types="node" /> directive. Your tsconfig.json does not need to include the config file. Keeping it outside include avoids emitting it into outDir.
By default @ttsc/lint discovers lint.config.{ts,cts,mts,js,cjs,mjs,json} by walking upward from the tsconfig directory, then (when that walk finds nothing) upward from the working directory. When a build integration compiles through a generated wrapper tsconfig in a temp directory (the bundler adapters do this whenever an alias or a compilerOptions overlay is set), the integration declares the real project root (pluginConfigDir on TtscCompiler) and discovery starts there instead of the temp tree. To override that discovery with an explicit path, pass configFile on the plugin entry in tsconfig.json (a relative path resolves against the same base directory):
// tsconfig.json
{
"compilerOptions": {
"plugins": [
{ "name": "@ttsc/lint", "configFile": "./tools/lint.config.ts" }
]
}
}configFile is the only host-owned key beyond name, enabled, stage, and transform, every other configuration lives inside the resolved lint.config.*.
See it
// src/index.ts
var x: number = 3;
let y: number = 4;
const z: string = 5;
console.log(x + y + z);$ npx ttsc --noEmit
src/index.ts:3:7 - error TS2322: Type 'number' is not assignable to type 'string'.
3 const z: string = 5;
~
src/index.ts:2:5 - error TS17397: [prefer-const] Use const instead of let.
2 let y: number = 4;
~~~~~~~~~~~~~
src/index.ts:1:1 - error TS11966: [no-var] Unexpected var, use let or const instead.
1 var x: number = 3;
~~~~~~~~~~~~~~~~~~Type errors (TS2322) and lint violations (TS17397, TS11966) come out in the same stream. Your CI step that already runs ttsc now gates lint too.
Next
→ Format, the format block keys and the rules they drive. → Rules, the lint rule catalog.