@ttsc/lint
v0.13.1
Published
Reference ttsc plugin: ESLint-style lint rules hosted in the same Program/Checker as the type-check pass.
Maintainers
Readme
@ttsc/lint

A linter and formatter. Co-protagonist of the ttsc toolchain — paired with ttsc, it replaces eslint and prettier.
140+ rules. Lint violations surface as error TSxxxxx from a single compile pass; the formatter applies via ttsc format.
Demonstration
Given this file:
// src/index.ts
var x: number = 3;
let y: number = 4;
const z: string = 5;
console.log(x + y + z);Run ttsc with @ttsc/lint enabled (see Setup):
$ pnpm ttsc
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;
~~~~~~~~~~~~~~~~~~
Found 3 errors in the same file, starting at: src/index.ts:3Type errors (TS2322) and lint violations (TS17397, TS11966) come out together. No second tool, no second CI step.
Setup
npm install -D ttsc @ttsc/lint @typescript/native-previewDrop a lint.config.ts next to your tsconfig.json:
// lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint";
export default {
format: {
printWidth: 100,
singleQuote: true,
trailingComma: "all",
},
rules: {
"no-var": "error",
"prefer-const": "error",
"no-explicit-any": "warning",
"no-console": "off",
},
} satisfies ITtscLintConfig;Run your normal ttsc or ttsx:
npx ttsc
npx ttsx src/index.tsErrors fail the command; warnings print without affecting the exit code. Under ttsx, errors stop the program before your entrypoint runs.
Fix and format
ttsc fix applies every autofix the enabled rules offer — lint and format together — writes results back to disk, then re-runs type-check + lint. ttsc format runs the format/* subset through the same dataflow.
npx ttsc fix
npx ttsc formatttsc fix is a one-shot project pass and rejects --watch, single-file mode, and --emit. Fixes are written to disk before the recheck runs, so source stays modified even when the command exits non-zero on remaining errors. Recommended flow: run ttsc fix locally, commit, then have CI run ttsc --noEmit to gate on zero remaining errors.
Configurations
Two top-level keys in lint.config.ts:
formatis a Prettier-style block that drives theformat/*autofixes. Format diagnostics are warnings and do not define compile failure policy.rulessets severity per lint rule."error"fails the build;"warning"prints without affecting the exit code;"off"disables the rule.
Format
The format block in lint.config.ts configures the formatter. Keys mirror .prettierrc:
// lint.config.ts
export default {
format: {
printWidth: 100,
singleQuote: true,
trailingComma: "all",
importOrder: ["<THIRD_PARTY_MODULES>", "@api(.*)$", "^[./]"],
jsdoc: true,
},
rules: { "no-var": "error" },
} satisfies ITtscLintConfig;Presence of the block (even empty format: {}) configures the always-on format rules at Prettier defaults for ttsc format. It does not make ttsc check fail on formatting by default; set format.severity only if you intentionally want check-time format diagnostics.
Each format key drives one rule:
| Rule | Driven by | Effect |
| --- | --- | --- |
| all format rules | severity (default "off") | Optional check-time diagnostic severity. ttsc format still applies configured format rules when this is off. |
| format/semi | semi | Insert trailing semicolons on ASI-terminated statements. |
| format/quotes | singleQuote | Convert quoted strings to the preferred quote style. |
| format/trailing-comma | trailingComma | Add trailing commas to multi-line lists. |
| format/print-width | printWidth, tabWidth, useTabs, endOfLine | Column-aware line reflow. Object/array literals, call/new arguments, and named import/export clauses break across lines when their flat form overflows the budget. |
| format/sort-imports | importOrder (opt-in) | Group external/relative imports and alphabetize each group + its specifiers. |
| format/jsdoc | jsdoc (opt-in) | Normalize JSDoc blocks toward prettier-plugin-jsdoc. |
format/sort-imports and format/jsdoc are opt-in: they only run when you set their format keys. Every other format rule is available to ttsc format as soon as a format block is present.
To disable or override one specific format rule, drop a sibling rules entry — rules wins on conflict:
export default {
format: { severity: "warning", semi: true },
rules: { "format/semi": "off" },
} satisfies ITtscLintConfig;Rules
Rules are off until you enable them:
// lint.config.ts
export default {
rules: {
"no-var": "error",
"eqeqeq": "error",
"prefer-template": "warning",
"no-non-null-assertion": "off",
},
} satisfies ITtscLintConfig;The rule corpus is tested in tests/test-lint/src/cases/*.ts, which is the best place to check the exact patterns currently covered. Each rule below links to its tested fixture:
adjacent-overload-signatures: keeps overload declarations for the same member adjacent.array-type: prefersT[]andreadonly T[]over array helper types.await-thenable: rejectsawaiton a value that is neither a Promise nor a thenable (type-aware).ban-ts-comment: rejects TypeScript suppression comments such as@ts-ignore.ban-tslint-comment: rejects obsoletetslint:comments.consistent-indexed-object-style: prefersRecordfor single index-signature object types.consistent-type-assertions: prefersastype assertions over angle-bracket assertions.consistent-type-definitions: prefers interfaces for object-shaped type definitions.consistent-type-imports: usesimport typewhen imported names are type-only.default-param-last: keeps parameters with default values at the end of the list.dot-notation: prefers dot property access when a string-literal key is a valid identifier.eqeqeq: requires strict equality operators.for-direction: catches loop counters updated in the wrong direction.no-alert: rejectsalert,confirm, andprompt.no-array-constructor: rejectsArrayconstructor calls.no-array-delete: rejectsdeleteon array elements.no-async-promise-executor: rejects async Promise executors.no-bitwise: rejects bitwise operators.no-caller: rejectsarguments.callerandarguments.callee.no-case-declarations: rejects lexical declarations directly insidecaseclauses.no-class-assign: rejects reassignment of class declarations.no-compare-neg-zero: rejects comparisons against-0.no-cond-assign: rejects assignments inside conditions.no-confusing-non-null-assertion: rejects confusing non-null assertions next to equality checks.no-console: rejectsconsolecalls.no-constant-condition: rejects constant conditions.no-continue: rejectscontinuestatements.no-control-regex: rejects control characters in regular expressions.no-debugger: rejectsdebuggerstatements.no-delete-var: rejects deleting variables.no-dupe-args: rejects duplicate function parameters.no-dupe-else-if: rejects repeatedelse ifconditions.no-dupe-keys: rejects duplicate object keys.no-duplicate-case: rejects duplicateswitchcase labels.no-duplicate-enum-values: rejects duplicate enum member values.no-dynamic-delete: rejectsdeleteon dynamically computed property keys.no-empty: rejects empty blocks.no-empty-character-class: rejects empty regex character classes.no-empty-function: rejects empty functions.no-empty-interface: rejects empty interfaces.no-empty-object-type: rejects empty object type literals.no-empty-pattern: rejects empty destructuring patterns.no-empty-static-block: rejects empty class static blocks.no-eq-null: rejects loose null comparisons.no-eval: rejectseval.no-ex-assign: rejects reassignment of caught exceptions.no-explicit-any: rejects explicitany.no-extra-bind: rejects unnecessary.bind()calls.no-extra-boolean-cast: rejects redundant boolean casts.no-extra-non-null-assertion: rejects repeated non-null assertions.no-fallthrough: rejects unmarkedswitchfallthrough.no-func-assign: rejects reassignment of function declarations.no-import-type-side-effects: hoists inlinetypemodifiers into a singleimport typedeclaration.no-inferrable-types: rejects type annotations TypeScript can infer.no-inner-declarations: rejects function declarations nested in blocks.no-irregular-whitespace: rejects irregular whitespace.no-iterator: rejects__iterator__.no-labels: rejects labels.no-lone-blocks: rejects unnecessary standalone blocks.no-lonely-if: rejectsifas the only statement in anelse.no-loss-of-precision: rejects number literals that lose precision.no-misleading-character-class: rejects misleading regex character classes.no-misused-new: rejects constructor-like signatures in interfaces.no-mixed-enums: rejects enums that mix numeric and string members.no-multi-assign: rejects chained assignments.no-multi-str: rejects multiline string escapes.no-namespace: rejects non-ambient namespaces.no-negated-condition: rejects negated conditions with anelse.no-nested-ternary: rejects nested ternary expressions.no-new: rejectsnewexpressions used only for side effects.no-new-func: rejectsFunctionconstructors.no-new-wrappers: rejects primitive wrapper constructors.no-non-null-asserted-nullish-coalescing: rejects non-null assertions next to??.no-non-null-asserted-optional-chain: rejects non-null assertions on optional chains.no-non-null-assertion: rejects postfix non-null assertions.no-obj-calls: rejects calling global objects as functions.no-object-constructor: rejectsnew Object().no-octal: rejects legacy octal literals.no-octal-escape: rejects octal escape sequences.no-plusplus: rejects++and--.no-promise-executor-return: rejects returned values from Promise executors.no-proto: rejects__proto__.no-prototype-builtins: rejects directObject.prototypemethod calls.no-regex-spaces: rejects repeated literal spaces in regexes.no-require-imports: rejects CommonJSrequireimports.no-return-assign: rejects assignments inreturn.no-script-url: rejectsjavascript:URLs.no-self-assign: rejects assignments to the same value.no-self-compare: rejects comparing a value to itself.no-sequences: rejects comma expressions.no-setter-return: rejects returned values from setters.no-shadow-restricted-names: rejects shadowing restricted globals.no-sparse-arrays: rejects sparse arrays.no-template-curly-in-string: rejects${...}text inside normal strings.no-this-alias: rejects aliasingthisto locals.no-throw-literal: rejects throwing literals.no-undef-init: rejects initializing toundefined.no-undefined: rejects the globalundefinedidentifier.no-unnecessary-type-constraint: rejects redundantextends anyandextends unknownconstraints.no-unneeded-ternary: rejects redundant ternary expressions.no-unsafe-declaration-merging: rejects unsafe class/interface declaration merging.no-unsafe-finally: rejects control flow fromfinally.no-unsafe-function-type: rejects the unsafeFunctiontype.no-unsafe-negation: rejects unsafe negation before relational checks.no-unused-expressions: rejects expression statements with no effect.no-unused-labels: rejects labels that nobreakorcontinuetargets.no-useless-call: rejects unnecessary.call()and.apply().no-useless-catch: rejects catch blocks that only rethrow.no-useless-computed-key: rejects unnecessary computed property keys.no-useless-concat: rejects unnecessary string concatenation.no-useless-constructor: rejects empty constructors with no parameters.no-useless-escape: rejects backslash escapes that have no effect inside strings or regexes.no-useless-rename: rejects import/export/destructure renames to the same name.no-var: rejectsvar.no-with: rejectswithstatements.no-wrapper-object-types: rejects boxed object type names such asStringandBoolean.object-shorthand: requires object property shorthand where possible.operator-assignment: prefers compound assignment operators.prefer-as-const: prefersas constfor literal assertions.prefer-const: prefersconstforletbindings that are never reassigned.prefer-enum-initializers: requires explicit enum member initializers.prefer-exponentiation-operator: prefers**overMath.pow.prefer-for-of: prefersfor...offor simple array iteration.prefer-function-type: prefers function type aliases over single-call interfaces.prefer-literal-enum-member: prefers literal enum member initializers over computed expressions.prefer-namespace-keyword: prefersnamespaceover TypeScript's legacymodulekeyword.prefer-spread: prefers spread arguments over.apply.prefer-template: prefers template literals over string concatenation.radix: requires a radix argument forparseInt.require-yield: requires generator functions to containyield.triple-slash-reference: rejects triple-slash reference directives.use-isnan: requiresNumber.isNaN/isNaNforNaNchecks.valid-typeof: restrictstypeofcomparisons to valid strings.vars-on-top: requiresvardeclarations at the top of their scope.yoda: rejects literal-first comparisons.
Third-party rule plugins
Other npm packages can ship lint rules that compile into the same @ttsc/lint binary and report through the same diagnostic stream as built-ins. Declare them in lint.config.ts:
// lint.config.ts
import demoPlugin from "ttsc-lint-plugin-demo";
import type { ITtscLintConfig } from "@ttsc/lint";
export default {
plugins: { demo: demoPlugin },
rules: { "demo/no-todo-comment": "error" },
} satisfies ITtscLintConfig;ttsc copies each declared contributor's Go source into a sub-package of @ttsc/lint's module at build time, so the resulting binary has both built-in and contributor rules registered before main. Authoring instructions and the public Go API live in the @ttsc/lint walkthrough → How a contributor package ships.
Contributor rules emit autofixes the same way built-ins do — call ctx.ReportFix(node, message, edits...) or ctx.ReportRangeFix(pos, end, message, edits...). The rule/astutil package re-exports the byte-range helpers built-ins use (NodeText, KeywordStart, FindKeyword, TokenRange). See the contributor autofix path section for the full contract and an example.
Sponsors
Thanks for your support.
Your donation encourages ttsc development.
