Skip to Content

TypeScript

TypeScript-only rules and @typescript-eslint plugin equivalents live under the typescript/* namespace.

Every rule here either requires TypeScript syntax or originates from a TS-aware @typescript-eslint extension with no plain ESLint counterpart.

TypeScript-only syntax includes interfaces, enum, namespace, as, !, import type, type parameters, declaration merging, parameter properties, and triple-slash references.

Generic JS/TS rules (such as eqeqeq, no-console) stay unnamespaced in Core.

This family deliberately mirrors typescript-eslint’s rule ids, but only under the typescript/* prefix. @ttsc/lint does not accept legacy bare names or @typescript-eslint/* aliases for these rules.

Source: typescript-eslint (MIT).

Rule index

Each rule name links to the detailed section below.

Examples come from the checked lint corpus  or package-level rule tests when project layout matters.

Disallow

Prefer

Require

Consistency

Other checks

Rules

typescript/adjacent-overload-signatures

Require overload declarations for the same member to be written adjacently.

Splitting overloads with other members hides the full signature set from readers and tools.

Example:

interface I { load(): void; save(): void; // reports: typescript/adjacent-overload-signatures (error) load(options: { cache: boolean }): void; }

typescript/array-type

Enforce one consistent spelling of array types.

By default the rule prefers T[] / readonly T[] over Array<T> / ReadonlyArray<T>, matching @typescript-eslint’s array-type default.

Example:

// reports: typescript/array-type (error) const a: Array<string> = [];

typescript/await-thenable

Reject await on operands that are not thenable, non-awaitable values passed to native Promise.all / allSettled / any / race, for await...of over values that are not async iterable, and await using of resources that are not async disposable.

Type-aware.

The Checker classifies awaitability as always, never, or uncertain; resolves aggregator receivers to the default-library PromiseConstructor; derives custom iterable and primitive-string values from their synchronous iterator protocol while retaining tuple-slot precision; and requires callable [Symbol.asyncIterator] and [Symbol.asyncDispose] members.

Protocol lookups resolve through unions (any callable async constituent is accepted), intersections, type aliases, inherited members, and generic constraints.

Ordinary non-thenable await diagnostics offer an opt-in quickfix.ttsc action that removes the keyword. ttsc fix and source.fixAll.ttsc do not apply it because removing even a non-thenable await changes microtask ordering. The other constructs are diagnostic-only.

Example:

async function bad(): Promise<number> { // reports: typescript/await-thenable (error) void Promise.all([42]); // reports: typescript/await-thenable (error) await using resource = { [Symbol.dispose](): void {}, }; // reports: typescript/await-thenable (error) for await (const value of [1, 2, 3]) { JSON.stringify([resource, value]); } // reports: typescript/await-thenable (error) return await 42; }

typescript/ban-ts-comment

Reject @ts-<directive> comments or require descriptions after directives.

The rule follows @typescript-eslint’s recommended defaults: @ts-ignore and @ts-nocheck are reported, @ts-check is allowed, and @ts-expect-error is allowed when followed by a description of at least three characters. @ts-nocheck is only reported where the compiler honors it — before the first statement. A reported @ts-ignore offers an opt-in quickfix.ttsc action that replaces it with @ts-expect-error. ttsc fix and source.fixAll.ttsc do not apply the replacement because an error-free following line would start producing TS2578.

Options:

  • minimumDescriptionLength?: number

    Minimum description length (counted with Unicode 16.0 extended grapheme clusters, so one emoji is one character) for directives configured as "allow-with-description" or { descriptionFormat }. Default: 3.

  • "ts-check"?, "ts-expect-error"?, "ts-ignore"?, "ts-nocheck"?: boolean | "allow-with-description" | { descriptionFormat: string }

    Per-directive policy: true reports every use, false allows the directive, "allow-with-description" allows it with a long-enough description, and { descriptionFormat } additionally requires the description to match the given regular expression. Defaults: "ts-check": false, "ts-expect-error": "allow-with-description", "ts-ignore": true, "ts-nocheck": true.

Example:

// reports: typescript/ban-ts-comment (error) // @ts-ignore const count: number = "oops"; // @ts-expect-error: TS2322 pinned by a regression test — allowed by default const flag: number = "oops";

typescript/ban-tslint-comment

Reject // tslint:disable and related TSLint directive comments left behind from the legacy TSLint era.

Example:

// reports: typescript/ban-tslint-comment (error) // tslint:disable const x = 1;

typescript/class-literal-property-style

Prefer a static readonly field over a get accessor whose body is a single return <literal>;.

The getter form re-runs the body on every read and obscures that the value is fixed; a readonly field is shorter, narrows to the literal type, and signals “this is a constant” at the call site.

Skipped when the class also declares a set accessor for the same member name, the setter’s side effects cannot be reproduced by a field.

Example:

class StringGetter { // reports: typescript/class-literal-property-style (error) static get label(): string { return "ttsc"; } }

typescript/consistent-indexed-object-style

Prefer Record<K, V> over { [key: K]: V } when an object type has a single index signature and no other members.

Example:

// reports: typescript/consistent-indexed-object-style (error) type Dict = { [key: string]: number }; const d: Dict = {};

typescript/consistent-type-assertions

Prefer the as form of type assertions over the angle-bracket form <T>expr, which is ambiguous inside JSX.

Example:

const input: unknown = "value"; // reports: typescript/consistent-type-assertions (error) const value = <string>input;

typescript/consistent-generic-constructors

Reject the redundant pattern where a variable is annotated with a generic type AND the same generic arguments are repeated on the constructor: const m: Map<K, V> = new Map<K, V>().

One of the two type-argument lists carries the binding; stating both is noise.

Example:

// reports: typescript/consistent-generic-constructors (error) const a: Map<string, number> = new Map<string, number>();

typescript/consistent-type-definitions

Enforce one consistent shape for object types.

By default the rule prefers interface over type aliases for plain object shapes.

Example:

// reports: typescript/consistent-type-definitions (error) type Shape = { name: string; };

typescript/consistent-type-exports

Require type-only re-exports to use export type { ... } instead of the value form export { ... } when the exported binding only refers to a type alias or interface in the same file.

The type form has no runtime cost and signals intent to the downstream import.

Example:

interface MixedType { ok: true; } const mixedValue = { ok: true }; // reports: typescript/consistent-type-exports (error) export { OnlyType };

typescript/consistent-type-imports

Require imports that only reference types to use import type {} so the import has no runtime cost.

Example:

// reports: typescript/consistent-type-imports (error) import { Foo } from "./types-fixture"; const x: Foo | null = null;

typescript/explicit-function-return-type

Require every exported function and method declaration to carry an explicit return-type annotation.

Implicit return types let downstream consumers depend on inference details that can shift with future edits; the explicit annotation pins the contract.

Example:

// reports: typescript/explicit-function-return-type (error) function noReturnType(x: number) { return x + 1; }

typescript/explicit-member-accessibility

Require an explicit accessibility modifier (public, private, or protected) on every class member declaration.

Implicit public is permitted by TypeScript but obscures intent, the modifier makes the encapsulation contract self-documenting. Members declared with the #name private-hash form are exempt.

Example:

class Implicit { // reports: typescript/explicit-member-accessibility (error) value: number = 0; }

typescript/method-signature-style

Prefer a function-property signature (f: () => void) over a shorthand method signature (f(): void) in interfaces and type literals so the strict-function-types contravariance check applies.

Example:

interface Service { // reports: typescript/method-signature-style (error) run(input: string): number; keep: (input: string) => number; }

typescript/no-array-delete

Reject delete arr[i] against array elements.

delete leaves a hole; use arr.splice to shrink the array.

Type-aware via the Checker. delete target[key] is spelled identically whether the target is an array or a Record / index-signature object, and on the latter it is the correct way to remove an entry — so the target’s type is the only thing that separates them, and the subscript’s syntactic shape says nothing.

Example:

const arr: number[] = [1, 2, 3]; // reports: typescript/no-array-delete (error) delete arr[0];

typescript/no-array-for-each

Prefer for ... of over Array.prototype.forEach(). The for-of form supports early termination (break/return) and await, while forEach swallows both.

Example:

// reports: typescript/no-array-for-each (error) [1, 2, 3].forEach((value) => {});

typescript/no-base-to-string

Reject string-coercion contexts (${x}, x + "", String(x)) where x has a type whose toString resolves to the default Object.prototype.toString and would print "[object Object]".

Type-aware via the Checker. Stringish primitives, Date, Error, arrays, regexes, and any object that overrides toString are safe.

Plain {} literals, Record<...> shapes, and structural interfaces with no toString member are the ones that produce the useless default string.

Example:

const plain = { id: 1 }; // reports: typescript/no-base-to-string (error) const message = `value: ${plain}`;

typescript/no-extraneous-class

Reject classes that exist purely as a namespace for static members or that are entirely empty.

A namespace import or plain functions are clearer than class Util { static foo() {} }, the class adds indirection without providing instance behavior.

Example:

// reports: typescript/no-extraneous-class (error) class StaticOnly { static factory(): number { return 1; } static readonly defaultValue = 42; }

typescript/no-confusing-non-null-assertion

Reject non-null assertions placed where they visually merge with a following operator, a! == b (reads as !=), a! in b, or a! instanceof B.

Wrap the assertion in parentheses ((a!) == b) or drop it entirely.

Example:

function f(x: number | null, y: number) { // reports: typescript/no-confusing-non-null-assertion (error) return x! === y; }

typescript/no-confusing-void-expression

Reject void X expressions used in any position where the surrounding context expects a value, initializer, call argument, return operand, conditional, binary, or ternary subexpression.

The only acceptable positions are an expression statement (void x;), an arrow function’s concise body (() => void x), and the operand of an enclosing void operator (void void x).

Example:

function writeAuditLog(): void {} // reports: typescript/no-confusing-void-expression (error) const a = void writeAuditLog();

typescript/no-deprecated

Reject references to declarations marked @deprecated in their JSDoc.

Type-aware via the Checker.

The rule resolves the symbol at each identifier, property access, call, new, or JSX tag-name location, walks the symbol’s declarations for an attached @deprecated JSDoc tag, and reports at the reference.

References inside the same declaration block as the deprecation marker are skipped so the deprecation site itself doesn’t fire.

Example:

// reports: typescript/no-deprecated (error) const a = oldFn();

typescript/no-duplicate-enum-values

Reject enum declarations whose members share the same literal value.

Reverse lookup (E[E.X]) returns whichever member is listed last, so duplicates almost always reflect a copy-paste mistake.

Example:

enum E { A = 1, B = 2, // reports: typescript/no-duplicate-enum-values (error) C = 1, }

typescript/no-dynamic-delete

Reject computed bracket-key delete expressions (delete obj[x]) where x is not a string literal, since these escape type tracking.

Example:

const key = "name"; const box: Record<string, string> = { name: "ttsc" }; // reports: typescript/no-dynamic-delete (error) delete box[key]; delete box["name"];

typescript/no-empty-interface

Reject empty interface declarations.

An empty interface that does not extends anything is equivalent to unknown and almost always represents incomplete typing work.

Example:

// reports: typescript/no-empty-interface (error) interface Empty {} const e: Empty = {};

typescript/no-empty-object-type

Reject {} as a type annotation.

{} matches every non-nullish value and is almost never intended; use Record<string, unknown> for a generic object, or object for any non-primitive.

Example:

// reports: typescript/no-empty-object-type (error) type T = {}; const v: T = {};

typescript/no-explicit-any

Reject any type annotations.

Typically configured as "warning" during incremental migrations.

Example:

function f( // reports: typescript/no-explicit-any (error) x: any, ): number { return Number(x); } f(0);

typescript/no-extra-non-null-assertion

Reject x!!, chained non-null assertions where the inner one already removes nullability. Autofixable: drops the extra !.

Example:

function f(x: number | null) { // reports: typescript/no-extra-non-null-assertion (error) return x!!; }

typescript/no-floating-promises

Reject discarded built-in Promises, invalid rejection-handler chains, and Promise-bearing arrays.

Type-aware via the Checker. A floating promise loses its rejection channel and runs out of order with surrounding code. A .catch or second .then argument handles rejection only when it is callable; .finally preserves the receiver chain’s handled state.

The scalar defaults ignore structural thenables, async IIFEs remain checked, and void is an explicit escape hatch. checkThenables, ignoreIIFE, ignoreVoid, allowForKnownSafeCalls, and allowForKnownSafePromises expose the upstream option boundary through typed rule tuples.

checkThenables selects what the rule examines, exactly as upstream does: it defaults to false, and turning it on reports more rather than less. Left off, a structural thenable is outside the rule entirely — neither a floating thenable nor a handled chain on one is reported. So a configuration carried over from a typescript-eslint project produces the same findings here.

Acceptable sinks are await, a callable rejection handler, assignment, the configured void escape, and return.

Example:

function getPromise(): Promise<number> { return Promise.resolve(1); } // reports: typescript/no-floating-promises (error) getPromise();

typescript/no-for-in-array

Reject for (const k in arr) where arr is statically typed as an array or tuple.

Type-aware via the Checker. for...in iterates enumerable property names (yielded as strings, including any inherited or custom-added members).

Not array values or numeric indices, almost always a mistake for for...of, Array#forEach, or an indexed for loop.

Example:

const numbers = [1, 2, 3]; function sideEffect(value: unknown): void { console.log(value); } // reports: typescript/no-for-in-array (error) for (const key in numbers) { sideEffect(key); }

typescript/no-import-type-side-effects

Hoist inline type modifiers on individual imports into a single top-level import type {}. Autofixable.

Example:

// reports: typescript/no-import-type-side-effects (error) import { type Bar, type Foo } from "./types-fixture"; const x: Foo | null = null; const y: Bar | null = null;

typescript/no-inferrable-types

Reject explicit type annotations that TypeScript can already infer from the initializer (const x: number = 1).

Example:

// reports: typescript/no-inferrable-types (error) const a: number = 5;

typescript/no-invalid-void-type

Reject void used as anything other than a function return type. void in a union (string | void) or as a non-allow-listed generic argument is almost always a confusion with undefined.

Allowed positions: function/method/arrow return-type annotations and generic arguments to Promise / Generator / AsyncGenerator / Iterator / AsyncIterator / IterableIterator / AsyncIterableIterator.

Example:

// reports: typescript/no-invalid-void-type (error) type Result = string | void;

typescript/no-magic-numbers

TypeScript-aware extension of no-magic-numbers that additionally ignores enum member values.

Example:

function isLong(value: number): boolean { // reports: typescript/no-magic-numbers (error) return value > 86400; }

typescript/no-meaningless-void-operator

Reject void X where X is already statically typed void, the void operator adds nothing because the operand already evaluates to undefined.

The operator is meaningful only on a value- returning expression that the caller wants to discard.

Type-aware, the Checker decides whether the operand carries the void type. The upstream checkNever option is left at its default of false: only void-typed operands trigger, never does not.

Example:

function voidReturning(): void {} // reports: typescript/no-meaningless-void-operator (error) void voidReturning();

typescript/no-misused-new

Reject signatures that fake a constructor or an instance new method, interface I { new (): I } (TypeScript treats this as the type of new I() regardless of intent) and class C { new(): C }.

Use a separate construct signature on a factory type when the intent is “anything callable with new”.

Example:

interface I { // reports: typescript/no-misused-new (error) constructor(): void; }

typescript/no-misused-promises

Reject thenables used as boolean values or supplied to contracts that cannot observe their asynchronous result.

The default checks cover boolean conditions and array predicates, Promise object spreads, synchronous using resources whose Symbol.dispose returns a thenable, and Promise-returning functions supplied where a void return is expected. Void-return positions include call/construct arguments, JSX attributes, inherited methods, object properties, returned callbacks, and variables. The rule uses resolved signatures and contextual types, so ordinary Promise-returning functions are covered and Promise-aware callback APIs remain valid regardless of their method name.

All three scalar options default to true. checksVoidReturn also accepts an object whose independently configurable keys are arguments, attributes, inheritedMethods, properties, returns, and variables; omitted keys stay enabled.

{ "rules": { "typescript/no-misused-promises": [ "error", { "checksConditionals": true, "checksSpreads": true, "checksVoidReturn": { "arguments": false } } ] } }

Example:

function getPromise(): Promise<boolean> { return Promise.resolve(true); } function sideEffect(): void { console.log("ready"); } async function inIf(): Promise<void> { // reports: typescript/no-misused-promises (error) if (getPromise()) { sideEffect(); } } declare function consume(callback: () => void): void; // reports: typescript/no-misused-promises (error) consume(() => Promise.resolve());

typescript/no-misused-spread

Reject spread expressions whose operand is syntactically wrong for the surrounding context.

AST-only subset of the upstream rule, no Checker required.

Fires on object literal spread inside an array literal or a call/construct argument ([...{ a: 1 }], f(...{ a: 1 })) and on array literal spread inside an object literal ({ ...[1, 2] }).

General iterability detection still needs the full type-aware rule.

Example:

// reports: typescript/no-misused-spread (error) const fromArr = [...{ a: 1 }];

typescript/no-mixed-enums

Reject enums that mix numeric and string members, which makes the resulting type unsafe for reverse lookups.

Example:

enum Mixed { A = 1, // reports: typescript/no-mixed-enums (error) B = "two", }

typescript/no-namespace

Reject non-ambient namespace and module Foo {} declarations in regular .ts files.

ES modules replace the legacy namespace concept; ambient declare namespace in .d.ts files stays allowed by default for global typings compatibility.

Example:

// reports: typescript/no-namespace (error) namespace Foo { export const x = 1; }

typescript/no-non-null-asserted-nullish-coalescing

Reject x! ?? y, the ! collapses null | undefined to a non-nullish value, so the ?? branch is unreachable.

Example:

const maybe: string | undefined = undefined; // reports: typescript/no-non-null-asserted-nullish-coalescing (error) const value = maybe! ?? "fallback";

typescript/no-non-null-asserted-optional-chain

Reject x!?.y, the non-null assertion makes the optional chain meaningless because the inner expression is already known to be defined.

Example:

const o: { a?: { b: number } } = {}; // reports: typescript/no-non-null-asserted-optional-chain (error) const x = o?.a!;

typescript/no-non-null-assertion

Reject postfix ! non-null assertions altogether.

The operator suppresses a real null / undefined possibility without inserting a check; prefer a narrowing branch, optional chaining, or refining the type at its source.

Example:

function f(x: number | null): number { // reports: typescript/no-non-null-assertion (error) return x!; } f(1);

typescript/no-redundant-type-constituents

Reject union and intersection type constituents that the type system absorbs anyway, string | any collapses to any.

T & never collapses to never, T & unknown collapses to T, and repeated constituents add nothing.

AST-only baseline: only the literal any / unknown / never keyword constituents and duplicates matched by textual identity are reported.

Subset relations such as string | "foo" and generic alias resolution still require the type-aware path.

Example:

// reports: typescript/no-redundant-type-constituents (error) type WithAny = string | any;

typescript/no-restricted-types

Reject exact type spellings from the configured types map. With no options or an empty map, the rule reports nothing.

Keys and source spellings are compared after removing whitespace, so NS . Legacy matches "NS.Legacy". A true entry uses the standard message, a string appends a custom message, and a structured entry may add fixWith plus editor-only suggest replacements. false and null entries disable a key.

This is intentionally a source-spelling policy: a configured local, shadowed, or imported alias is still restricted, while a qualified name only matches its exact qualified key. Type queries and runtime value expressions are not type-reference matches.

Example:

export default { rules: { "typescript/no-restricted-types": [ "error", { types: { Legacy: { message: "Use Modern instead.", fixWith: "Modern", suggest: ["Safe", "UnknownLegacy"], }, "NS.Legacy": true, }, }, ], }, };

typescript/no-require-imports

Reject require(...) calls and import x = require(...) declarations.

Use ES module import syntax so the type-only / runtime-import distinction is preserved and declaration shape stays consistent.

Example:

// reports: typescript/no-require-imports (error) const fs = require("fs");

typescript/no-this-alias

Reject aliasing this to a local (const self = this, const that = this, destructuring const { x } = this).

Arrow functions and .bind(this) make the workaround unnecessary; the alias also breaks type narrowing on this.

Example:

class A { m() { // reports: typescript/no-this-alias (error) const self = this; return self; } }

typescript/no-unnecessary-boolean-literal-compare

Reject direct comparison of a boolean-typed value with true / false literals, x === true is just x, x !== false is just x.

The literal compare adds noise without changing the result whenever the non-literal side is provably pure boolean.

Type-aware via the Checker. Skipped when the value side carries null / undefined, the literal compare is also stripping nullability there, and the rewrite would lose information.

Example:

const flag = true; // reports: typescript/no-unnecessary-boolean-literal-compare (error) const yes = flag === true;

typescript/no-unnecessary-condition

Reject conditions whose static type proves the runtime truthiness is fixed, if ({}), if (null), while (""), 0 && f().

The conditional becomes either dead code (the always-false arm) or unconditional execution (the always-true arm); the explicit shape (if (true), deleting the dead branch, or widening the type) names the intent.

Type-aware via the Checker. The rule stays silent on any, unknown, plain boolean, plain string, plain number, unions that mix truthy and falsy outcomes.

And any other shape whose runtime truthiness is not statically decidable, only the provably always-truthy / always-falsy positions are flagged.

Example:

const obj = { value: 1 }; function sideEffect(value: unknown): void { console.log(value); } // reports: typescript/no-unnecessary-condition (error) if (obj) { sideEffect(obj); }

typescript/no-unnecessary-parameter-property-assignment

Reject this.x = x in a constructor body when x is already declared as a parameter property, TypeScript performs the assignment automatically.

Example:

class Repeated { constructor( public value: string, private readonly count: number, normal: string, ) { // reports: typescript/no-unnecessary-parameter-property-assignment (error) this.value = value; // reports: typescript/no-unnecessary-parameter-property-assignment (error) this.count = count; this.normal = normal; } }

typescript/no-unnecessary-qualifier

Reject Foo.Bar references written inside namespace Foo { ... } or enum Foo { ..., X = Foo.Y, ... } where the Foo. qualifier is redundant — the unqualified name already resolves to the same binding.

Type-aware via the Checker. The qualifier is flagged only when the head resolves to a namespace or enum symbol declared by one of the enclosing declarations, and the member name — looked up unqualified from the same location — resolves to that same symbol.

A shadowed member (a local bar hiding Foo.bar) or a shadowed namespace name (a local Foo object) keeps its qualifier: dropping it would silently change which binding the code reads.

Example:

namespace Foo { export const Bar = 1; // reports: typescript/no-unnecessary-qualifier (error) export const alias = Foo.Bar; }

typescript/no-unnecessary-template-expression

Reject template literals that carry no template-only behavior, a ${"abc"} interpolation, a lone ${name} span around a string-typed value, or a abc no-substitution literal with no escaped backticks.

Each of these collapses to a regular string literal without changing meaning, so the backtick form is just noise.

Type-aware via the Checker. The interpolation forms are flagged only when the span’s expression statically resolves to a string-like type and the surrounding head / tail text is empty.

The runtime coercion is then a no-op, mirroring the upstream rule.

Example:

// reports: typescript/no-unnecessary-template-expression (error) const a = `${"abc"}`;

typescript/no-unnecessary-type-arguments

Reject Foo<DefaultT> calls where the supplied generic argument is the same as the parameter’s default, the argument adds nothing.

Type-aware via the Checker. Walks the resolved type parameters of the call’s signature and compares each explicit argument against the parameter’s declared default.

Only the contiguous trailing run of default-equal arguments is reported, TypeScript can only omit a suffix of the type-argument list.

Example:

function withDefault<T = string>(value?: T): T | undefined { return value; } // reports: typescript/no-unnecessary-type-arguments (error) const a = withDefault<string>("hello");

typescript/no-unnecessary-type-assertion

Reject x as T and <T>x assertions whose target type is the same as x’s already-known static type, the assertion adds nothing.

Also rejects x! non-null assertions when x is already statically non-nullable. The as const syntax is excluded because it produces a strictly different type and is handled by the prefer-as-const rule.

Type-aware via the Checker.

Example:

const definitelyString: string = "ready"; // reports: typescript/no-unnecessary-type-assertion (error) const a = definitelyString as string;

typescript/no-unnecessary-type-constraint

Reject <T extends unknown> and similar constraints that match everything. Autofixable: drops the constraint while retaining the <T,> disambiguation required by single-parameter generic arrows in .tsx, .mts, and .cts files.

Example:

// reports: typescript/no-unnecessary-type-constraint (error) function identity<T extends unknown>(value: T): T { return value; }

typescript/no-unsafe-argument

Reject passing an any-typed value into a parameter whose type is concrete. The call still runs, but every static guarantee the function’s signature would otherwise enforce is silently dropped.

Type-aware via the Checker. unknown is not flagged.

Example:

const anyValue: any = JSON.parse("{}"); function takesNumber(value: number): void { console.log(value); } // reports: typescript/no-unsafe-argument (error) takesNumber(anyValue);

typescript/no-unsafe-assignment

Reject direct and nested any values escaping through assignment boundaries. The rule covers annotated and inferred variables, reassignments, parameter and binding defaults, class fields and auto-accessors, contextual object and JSX properties, array spreads, and array or object destructuring.

Type-aware via the Checker. Matching generic references are compared recursively with cycle protection, including tuples and mutable or readonly arrays. any may flow into unknown, including a corresponding nested generic argument, because unknown still requires narrowing before use. Structurally different reference targets are not compared by walking arbitrary properties.

The rule has no options.

Example:

const anyValue: any = JSON.parse("{}"); // reports: typescript/no-unsafe-assignment (error) const num: number = anyValue; // reports: typescript/no-unsafe-assignment (error) const inferred = anyValue; const boundary: unknown = anyValue; // allowed // reports: typescript/no-unsafe-assignment (error) const nested: Set<Set<string>> = new Set<Set<any>>();

typescript/no-unsafe-call

Reject calling a value whose static type is any. The runtime call still happens, but the signature is unchecked and the return type spreads any to every downstream use.

Type-aware via the Checker. Visits plain calls, new, and tagged templates. unknown is not flagged.

Example:

const anyValue: any = JSON.parse("{}"); // reports: typescript/no-unsafe-call (error) anyValue();

typescript/no-unsafe-declaration-merging

Reject declaration merging between an interface and a class with the same name.

The interface grafts members onto the class type without forcing a runtime implementation, so the class object lies about what it exposes.

Example:

class Merged { value = 1; } // reports: typescript/no-unsafe-declaration-merging (error) interface Merged { other: string; }

typescript/no-unsafe-enum-comparison

Reject == / === / != / !== comparisons between an enum-typed value and a plain number or string of the same widened primitive.

The comparison silently accepts unrelated enums and raw literals that happen to share the underlying primitive.

Type-aware via the Checker. The rule fires when one side carries an enum type and the other is the widened primitive without naming the same enum.

Comparisons against members of the same enum, and against any / unknown / never (to keep generic helpers quiet), pass through.

Example:

enum Color { Red = "red", Blue = "blue", } const color: Color = Color.Red; // reports: typescript/no-unsafe-enum-comparison (error) const matchesRed = color === "red";

typescript/no-unsafe-function-type

Reject the unsafe Function type, which matches every callable regardless of signature.

Declare the specific call signature instead.

Example:

// reports: typescript/no-unsafe-function-type (error) type Callback = Function;

typescript/no-unsafe-member-access

Reject property access on a receiver whose static type is any. The lookup still resolves at runtime, but the property type is any and spreads through the rest of the program.

Type-aware via the Checker. Visits dot access and computed element access. unknown is not flagged.

Example:

const anyValue: any = JSON.parse("{}"); // reports: typescript/no-unsafe-member-access (error) const prop = anyValue.displayName;

typescript/no-unsafe-return

Reject a return expression whose static type is any from a function whose declared return type is a concrete (non-any / non-unknown / non-void) shape.

The any leaks past the type boundary and disables every downstream check on the returned value.

Type-aware via the Checker. The rule walks each return to its enclosing function-like declaration, asks the Checker for that function’s signature, and compares the declared return type against the expression type.

unknown-typed expressions pass through because they cannot be used without further narrowing.

Example:

function asNumber(): number { // reports: typescript/no-unsafe-return (error) return anyValue; }

typescript/no-unsafe-unary-minus

Reject the unary - operator applied to an operand whose static type is not number-like or bigint-like, -x silently coerces strings, objects, and other shapes via Number(x) and almost always indicates a bug.

Type-aware via the Checker. any / unknown / never operands pass through to match the upstream allowAny-style defaults that keep generic helpers quiet.

Example:

const text = "42"; // reports: typescript/no-unsafe-unary-minus (error) const a = -text;

typescript/no-useless-constructor

TypeScript-aware extension of no-useless-constructor that tolerates a constructor existing solely to expose parameter properties.

Example:

class EmptyNoParams { // reports: typescript/no-useless-constructor (error) constructor() {} }

typescript/no-useless-empty-export

Reject redundant export {} declarations in module files.

The file is already a module via its other top-level import / export.

Example:

export const marker = 1; // reports: typescript/no-useless-empty-export (error) export {};

typescript/no-wrapper-object-types

Reject the wrapper object types String, Number, Boolean, Symbol, and BigInt.

Autofixable to the corresponding primitive. Object stays detection-only because it has slightly different semantics.

Example:

// reports: typescript/no-wrapper-object-types (error) type Name = String;

typescript/non-nullable-type-assertion-style

Reject x as Foo assertions whose target type is the non-nullable version of x’s static type, replace with the shorter x! non-null assertion.

Type-aware via the Checker. Fires when the source expression’s static type is Foo | null, Foo | undefined, or Foo | null | undefined, and the asserted type equals the non-nullable subset.

Example:

function readValue(maybeUndefined: string | undefined) { // reports: typescript/non-nullable-type-assertion-style (error) return maybeUndefined as string; }

typescript/only-throw-error

Reject throw X where X is statically known not to derive from Error, string literals, numbers, plain object literals, and the like.

Type-aware via the Checker. Non-Error throws lose the stack trace and confuse instanceof checks in the surrounding catch.

Example:

function throwStringLiteral(): never { // reports: typescript/only-throw-error (error) throw "boom"; }

typescript/parameter-properties

Reject TypeScript parameter-property constructors (constructor(public foo: T)).

Prefer plain field declarations so the class shape is visible from the member list instead of buried inside the constructor parameter list.

AST-only, the trigger is a syntactic modifier (public / private / protected / readonly / override) on a constructor parameter.

Example:

class ParameterShorthand { constructor( // reports: typescript/parameter-properties (error) public name: string, // reports: typescript/parameter-properties (error) private readonly id: number, // reports: typescript/parameter-properties (error) protected count: number, ) {} }

typescript/prefer-as-const

Prefer as const over a literal type that repeats the literal it describes: as "literal" / <"literal"> assertions, plus variable and class-property annotations such as let x: "literal" = "literal". Literals are compared by raw source spelling, so 'value' as "value" and let n: 10 = 0xa stay clean.

Assertions are autofixable (the literal type becomes const in place). Annotation findings offer an editor quick fix that removes the annotation and appends as const after the initializer. That larger rewrite remains suggestion-only and is never applied by ttsc fix.

Example:

// reports: typescript/prefer-as-const (error) const status = "ready" as "ready"; // reports: typescript/prefer-as-const (error) let stage: "init" = "init"; class Task { // reports: typescript/prefer-as-const (error) state: "idle" = "idle"; }

typescript/prefer-enum-initializers

Require every enum member to have an explicit initializer.

Implicit auto-increment is fine for novelty enums but dangerous once a value gets persisted.

Example:

enum E { // reports: typescript/prefer-enum-initializers (error) A, }

typescript/prefer-find

Prefer arr.find(predicate) over arr.filter(predicate)[0] and arr.filter(predicate).at(0).

Type-aware via the Checker. Fires only when the receiver of filter is provably an array or tuple.

find short-circuits on the first match instead of materializing the whole filtered array, so it expresses the “get me the first match” intent more directly and is strictly faster on large inputs.

Non-zero index accesses ([1], .at(1), …) are intentionally skipped because find cannot express them.

Example:

// reports: typescript/prefer-find (error) const first = list.filter((s) => s.length > 0)[0];

typescript/prefer-function-type

Prefer a type alias over an interface that declares only a single call signature, the type form composes better with structural typing.

Example:

// reports: typescript/prefer-function-type (error) interface F { (x: number): string; }

typescript/prefer-includes

Prefer array.includes(x) over array.indexOf(x) !== -1 (and the matching === -1, >= 0, < 0, > -1 shapes).

Type-aware via the Checker. Fires only when the receiver of indexOf is provably an array, tuple, or string.

The includes-style call states the intent directly and avoids the sentinel-vs-position confusion that comes with comparing an indexOf return value against -1.

Example:

const list = ["a", "b"]; function sideEffect(value: unknown): void { console.log(value); } // reports: typescript/prefer-includes (error) if (list.indexOf("a") !== -1) { sideEffect(list); }

typescript/prefer-literal-enum-member

Prefer literal initializers (= 0, = "FOO") for enum members over computed expressions, so the value is decidable at compile time.

Example:

const base = 1; enum Value { Fixed = 1, // reports: typescript/prefer-literal-enum-member (error) Computed = base + 1, }

typescript/prefer-namespace-keyword

Prefer the namespace keyword over the legacy module Foo {} form. Autofixable.

Example:

// reports: typescript/prefer-namespace-keyword (error) module Foo { export const x = 1; }

typescript/prefer-nullish-coalescing

Prefer ?? over || (and ??= over ||=, and ?? over the ternary x ? x : y) when the intent is to default null / undefined.

|| short-circuits on every falsy value (0, "", false, NaN), so “default this if missing” silently coerces legitimate zeros and empty strings.

The AST-only baseline skips operands the surrounding context already coerces to boolean (if (a || b), !(a || b), ternary condition, etc.).

Example:

const maybe: string | undefined = undefined; const other = "fallback"; // reports: typescript/prefer-nullish-coalescing (error) const a = maybe || other;

typescript/prefer-optional-chain

Prefer an optional chain (a?.b?.c) over chained boolean guards such as a && a.b && a.b.c or a != null && a.b.

The optional-chain form is shorter and short-circuits to undefined instead of the leftmost falsy value, which is almost always the intent when guarding a property access against a nullish base.

AST-only: the rule matches by the textual identity of the guard against the receiver of the right-hand access, and skips chains that cross a call expression with arguments.

Example:

const obj: { profile?: { name?: string; load?(): string } } | null = { profile: { name: "Ada" }, }; // reports: typescript/prefer-optional-chain (error) const profile = obj && obj.profile;

typescript/prefer-promise-reject-errors

Reject Promise.reject(value) where value is statically known not to derive from Error, string literals, numbers, plain primitives, and the like.

Type-aware via the Checker. Mirrors typescript/only-throw-error for the rejection side of the promise contract.

A non-Error rejection loses the structured stack trace and breaks downstream instanceof Error checks in .catch(...) / try { await ... } catch (err) handlers.

Covers Promise.reject(arg), <promise>.reject(arg) on a Promise-typed receiver, and reject(arg) calls bound to the second parameter of a new Promise((_, reject) => ...) executor.

any and unknown pass through, matching upstream’s allowThrowingAny / allowThrowingUnknown defaults.

Example:

function rejectStringLiteral(): Promise<never> { // reports: typescript/prefer-promise-reject-errors (error) return Promise.reject("boom"); }

typescript/prefer-readonly

Reject private class fields that could carry readonly.

AST-only baseline: fires on a PropertyDeclaration inside a class body that is private (or uses the #name private-hash form), does not already carry readonly, and is initialized at the declaration site.

A field initialized at declaration time is set before the constructor runs, so locking it as readonly rules out accidental reassignments without changing runtime behavior.

The fully type-aware upstream rule also walks assignments inside the constructor and other methods; the AST baseline targets the narrow but safe shape.

Example:

class Foo { // reports: typescript/prefer-readonly (error) private a = 1; }

typescript/prefer-reduce-type-parameter

Prefer arr.reduce<T>(callback, initial) over the assertion-on- initial-value pattern arr.reduce(callback, initial as T).

Type-aware via the Checker. The accumulator type bound through a call-site type parameter constrains the callback’s accumulator parameter directly.

An as on the seed only widens the runtime value and lets the callback infer the accumulator’s parameter type from the seed’s widened literal shape instead.

The rule fires only when the .reduce receiver is provably an array or tuple, the call has no existing type arguments, and the second argument is an as or angle-bracket type assertion.

Example:

const intoSet = names.reduce( (acc, name) => { acc.add(name); return acc; }, // reports: typescript/prefer-reduce-type-parameter (error) new Set<string>() as Set<string>, );

typescript/prefer-regexp-exec

Prefer re.exec(str) over str.match(re) when the regex has no g flag.

Type-aware via the Checker. Both shapes return the same RegExpExecArray | null for first-match queries, but String#match silently switches to “every match” the moment the regex gains the g flag.

A typo at the regex literal changes the call’s return shape from [fullMatch, ...captures] to a flat string[] of matches. The AST-only baseline reads the flag suffix off the regex literal directly.

Non-literal regex arguments (a new RegExp(...), a variable holding /.../) are conservatively skipped because static flag tracking would explode in scope.

Example:

// reports: typescript/prefer-regexp-exec (error) const match = text.match(/invoice/);

typescript/prefer-return-this-type

When an instance method always return this, declare its return type as this instead of the enclosing class name so subclass call sites keep the narrower receiver type and method-chaining stays polymorphic.

Type-aware via the Checker. Fires only when the method declares an explicit non-this return-type annotation AND every value-returning return statement in the body returns exactly this.

Methods with no annotation, methods with at least one non-this return, async methods, generators, constructors.

Accessors, and static methods are skipped, each has return-shape semantics the this rewrite does not preserve.

Example:

function sideEffect(name: string): void { console.log(name); } class Chainable { // reports: typescript/prefer-return-this-type (error) setName(name: string): Chainable { sideEffect(name); return this; } }

typescript/prefer-string-starts-ends-with

Prefer str.startsWith(prefix) / str.endsWith(suffix) over the indexOf / lastIndexOf and anchored-regex idioms, str.indexOf(p) === 0.

str.indexOf(p, str.length - p.length) !== -1, str.lastIndexOf(p) === str.length - p.length, /^prefix/.test(str), and /suffix$/.test(str).

Type-aware via the Checker. Fires only when the string-position subject is provably a string-like type.

The dedicated methods state the intent directly and avoid the off-by-one arithmetic and regex-anchor pitfalls of the older shapes.

Example:

const text = "https://example.com"; const needle = "https"; // reports: typescript/prefer-string-starts-ends-with (error) const a = text.indexOf(needle) === 0;

typescript/promise-function-async

Require functions whose return type is Promise<T> (or a Promise-like thenable) to be declared with the async keyword.

Type-aware via the Checker. An async function wraps a synchronous throw into a rejected Promise so the caller’s await / .catch(...) observes it.

The non-async equivalent throws synchronously and bypasses every Promise-aware handler downstream. Marking the function async keeps the rejection channel consistent with the declared return type.

Overload signatures, abstract methods, and declaration-merging hosts (functions with no body) are skipped, there is no body to wrap.

Example:

// reports: typescript/promise-function-async (error) function makePromise(): Promise<number> { return getPromise(); }

typescript/related-getter-setter-pairs

Reject a get accessor whose declared return type does not match the parameter type of its companion set accessor on the same class.

The accessor pair presents a single conceptual field to callers, obj.x = v followed by w = obj.x should round-trip with compatible types, but TypeScript otherwise lets the two accessors carry independent annotations.

Type-aware. The comparison resolves both sides through the Checker so type aliases, generic parameters, and union constituents collapse to the same set of values before equality is decided.

Example:

class Mismatch { private _value = "abc"; // reports: typescript/related-getter-setter-pairs (error) get value(): string { return this._value; } set value(next: number) { this._value = String(next); } }

typescript/require-array-sort-compare

Require arr.sort() and arr.toSorted() calls to pass an explicit compareFunction.

Without a comparator both methods coerce elements to strings and sort lexically, so [10, 2, 1].sort() returns [1, 10, 2], almost never the intent.

Type-aware via the Checker: only fires when the receiver is provably an array or tuple, so user-defined methods named sort / toSorted on non-array types do not trigger the rule.

Example:

const numbers = [3, 1, 2]; // reports: typescript/require-array-sort-compare (error) numbers.sort();

typescript/require-await

Reject async functions that have no await expression and return no promise.

An async function with no await only inflates the return type to Promise<T> without doing any asynchronous work; collapse it to a sync function.

Type-aware via the Checker, which upstream uses for the same purpose: a function that returns a promise is accepted without an await, because marking it async states the contract for callers and forwards the inner promise. A for await loop counts as an await — it is spelled as a for-of carrying an await modifier rather than as an await expression.

Async generators are accepted as long as they have at least one yield.

Example:

function syncWork(): number { return 1; } // reports: typescript/require-await (error) async function noAwait(): Promise<number> { return syncWork(); }

typescript/restrict-plus-operands

Reject + expressions whose operands are not both number, both string, or both bigint.

Type-aware via the Checker. 1 + "a", null + 5, and obj + 1 are silently coerced by the runtime, almost always a bug.

Mixed number/string concatenations are likewise rejected: the author should convert explicitly with String(x) or use a template literal.

Example:

// reports: typescript/restrict-plus-operands (error) const a = 1 + "a";

typescript/restrict-template-expressions

Reject template-literal interpolations whose expression carries a type that does not stringify cleanly, ${obj} prints "[object Object]", ${null} prints "null", and so on.

Type-aware via the Checker. Each ${...} span’s type must be string-like, number-like, bigint-like, or boolean-like.

any, unknown, and never pass through to avoid false positives on generic helpers, matching upstream allowAny / allowNever defaults. Union and intersection types must have every constituent stringify cleanly.

Example:

const obj = { id: 1 }; const maybe: string | null = null; // reports: typescript/restrict-template-expressions (error) const a = `id=${obj}`; // reports: typescript/restrict-template-expressions (error) const b = `value=${null}`; // reports: typescript/restrict-template-expressions (error) const c = `value=${undefined}`; // reports: typescript/restrict-template-expressions (error) const d = `value=${maybe}`;

typescript/return-await

Reject return promise inside try, catch, or finally; require return await promise.

Without the await, the surrounding handler unbinds before the promise settles, so a rejection skips the catch block entirely and the finally cleanup races the result.

Example:

function getPromise(): Promise<number> { return Promise.resolve(1); } async function returnInsideTry(): Promise<number> { try { // reports: typescript/return-await (error) return getPromise(); } catch (err) { return 0; } }

typescript/sort-type-constituents

Require union and intersection type constituents to be listed in a canonical group order, keyword primitives alphabetized first, named / object references alphabetized next, null / undefined last.

Two authors writing the same set of values can otherwise disagree on the spelling, and the canonical order eliminates that discussion from review.

AST-only baseline: ordering is decided by syntactic group (keyword primitive vs. named reference vs. nullish) and a within-group source-text sort.

Nested unions / intersections are skipped, only the outermost shape, the one the author wrote, fires the diagnostic.

Example:

// reports: typescript/sort-type-constituents (error) type OutOfOrderPrimitives = string | number;

typescript/strict-boolean-expressions

Reject non-boolean values used in a boolean context.

Type-aware via the Checker. Fires when the test of an if, while, do, for, or ternary, the operand of !, or either side of && / || carries a type whose flags are not pure boolean.

Numbers (if (count) is truthy for any non-zero), strings ("" is falsy), and nullable objects (if (obj) conflates null / undefined with a present object) all silently coerce in boolean position.

An explicit comparison (count !== 0, str.length > 0, obj != null) names the intent.

Example:

const count = 1; function sideEffect(value: unknown): void { console.log(value); } // reports: typescript/strict-boolean-expressions (error) if (count) { sideEffect(count); }

typescript/switch-exhaustiveness-check

Require every enumerable member of a switch discriminant to be covered by an explicit case by default.

Type-aware via the Checker. The rule handles singleton literals, string/number/bigint/boolean literal unions, enums, null, undefined, unique symbols, constrained generics, and finite pieces of intersections. Open pieces such as string cannot be enumerated, but they do not hide adjacent finite members such as the undefined in string | undefined.

A real default does not suppress missing finite members under the scalar defaults. When every missing member has a safely accessible value name, the diagnostic offers an opt-in quickfix.ttsc action that inserts branches before a real or recognized comment default; ttsc fix and source.fixAll.ttsc never apply these throw-stub suggestions automatically.

Options:

  • allowDefaultCaseForExhaustiveSwitch (default true) allows a default after every finite member is explicitly covered. Set it to false to report that redundant default.
  • considerDefaultExhaustiveForUnions (default false) lets a real or recognized comment default satisfy otherwise missing finite members.
  • requireDefaultForNonUnion (default false) requires a real or recognized comment default when the discriminant contains an open, non-literal branch.
  • defaultCaseCommentPattern recognizes the final comment after the last case as a default marker. The fallback is the case-insensitive Unicode pattern /^no default$/iu; custom strings use Go’s RE2 regular-expression syntax and are not made case-insensitive automatically.

Example:

type Tag = "a" | "b" | "c"; function sideEffect(value: string): void { console.log(value); } function handle(tag: Tag): void { // reports: typescript/switch-exhaustiveness-check (error) switch (tag) { case "a": sideEffect("a"); break; case "b": sideEffect("b"); break; default: sideEffect("fallback"); break; } }

typescript/triple-slash-reference

Reject /// <reference path="..." />, /// <reference types="" />, and /// <reference lib="" /> directives.

Replace with import (or import type) declarations and compilerOptions.types in tsconfig.json.

Example:

// reports: typescript/triple-slash-reference (error) /// <reference path="./other-fixture.d.ts" /> const x = 1;

typescript/unbound-method

Reject referencing a class instance method as a value instead of calling it (obj.method passed as a callback, aliased to a variable, or stored on another object).

Type-aware via the Checker. JavaScript methods are not bound to their receiver, once extracted, this resolves to whatever the caller supplies (usually undefined in strict mode or the host object the value lands on).

Safe positions, immediate call (obj.method()), assignment target, typeof / delete operand, instanceof / in operand, and tagged-template tag, pass through.

Static methods are exempt because the constructor is stably bound.

Example:

const g = new Greeter(); // reports: typescript/unbound-method (error) const captured = g.greet;

typescript/use-unknown-in-catch-callback-variable

Require the callback parameter of .catch(...) and the second argument of .then(...) to be typed unknown.

Mirrors TypeScript 4.4+ useUnknownInCatchVariables, which made catch (e) default to unknown, the same discipline applied to promise rejection handlers so a rejection cannot smuggle in an implicit any.

Example:

function getPromise(): Promise<number> { return Promise.resolve(1); } function sideEffect(): void { console.log("handled"); } async function catchNoAnnotation(): Promise<void> { // reports: typescript/use-unknown-in-catch-callback-variable (error) getPromise().catch((err) => { sideEffect(); }); }
Last updated on