Skip to Content

Core

Generic ESLint-compatible rules that apply to both JavaScript and TypeScript source.

Every rule listed here corresponds 1-to-1 with an ESLint core rule of the same kebab-case id.

TypeScript-only rules and @typescript-eslint extension rules do not live here. They belong to TypeScript.

Keeping the core family namespace-free means projects migrating from ESLint can paste their rule severities into a ttsc.lint.config.ts file without renaming anything.

The fix availability noted in each rule’s JSDoc applies when ttsc fix or the LSP code action is invoked; a rule marked Autofixable may still produce diagnostics that require human review for edge cases.

Source: ESLint core rules  (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

  • no-alert: Reject calls to alert, confirm, and prompt.
  • no-array-constructor: Reject Array(...) and new Array(...) constructor calls.
  • no-async-promise-executor: Reject new Promise(async (resolve, reject) => { ... }).
  • no-await-in-loop: Reject explicit and implicit awaits evaluated in repeated loop positions.
  • no-bitwise: Reject bitwise operators.
  • no-caller: Reject arguments.caller and arguments.callee, both deprecated properties forbidden in strict mode.
  • no-case-declarations: Reject lexical declarations inside case or default clauses.
  • no-class-assign: Reject reassigning a class binding.
  • no-compare-neg-zero: Reject comparisons against -0.
  • no-cond-assign: Reject assignment expressions inside conditions.
  • no-console: Reject calls to console.*.
  • no-constant-condition: Reject conditions whose value can be determined statically.
  • no-continue: Reject continue statements.
  • no-constructor-return: Reject return X; (with a value) inside a class constructor.
  • no-control-regex: Reject ASCII control characters inside regular expression literals and RegExp strings.
  • no-debugger: Reject debugger statements.
  • no-delete-var: Reject delete applied to plain variable bindings.
  • no-dupe-args: Reject function f(a, a) and similar parameter lists.
  • no-dupe-class-members: Reject two declarations of the same member on a single class.
  • no-dupe-else-if: Reject if (a) {} else if (a) {}, the second branch is unreachable.
  • no-dupe-keys: Reject { a: 1, a: 2 }, duplicate property keys in an object literal silently overwrite earlier values.
  • no-duplicate-case: Reject the same case label appearing twice in a switch, later duplicates are unreachable.
  • no-duplicate-imports: Reject a repeated module specifier when the import declarations could be merged into one legal declaration.
  • no-else-return: Reject an else block whose preceding if branch already ends in a return.
  • no-empty: Reject uncommented empty blocks and switches.
  • no-empty-character-class: Reject empty regex character classes.
  • no-empty-function: Reject uncommented empty function and method bodies unless their category is allowed.
  • no-empty-named-blocks: Reject empty named import or export clauses, import {} from "x", import name, {} from "x", and export {}.
  • no-empty-pattern: Reject empty destructuring patterns.
  • no-empty-static-block: Reject uncommented empty static {} class initialization blocks.
  • no-eq-null: Reject loose null comparisons.
  • no-eval: Reject eval(...) and indirect eval calls, almost always a security or correctness bug.
  • no-ex-assign: Reject reassigning the parameter of a catch clause.
  • no-extend-native: Reject assignments to a built-in prototype such as Array.prototype.foo = bar.
  • no-extra-bind: Reject .bind(thisArg) when the function cannot use the bound receiver.
  • no-extra-boolean-cast: Reject redundant boolean casts such as !!Boolean(x), if (Boolean(x)), or Boolean(!!x).
  • no-fallthrough: Reject switch cases that can reach the next label without an intentional // falls through comment.
  • no-func-assign: Reject reassignment of function declarations.
  • no-implicit-coercion: Reject common implicit-coercion idioms.
  • no-import-assign: Reject writes to a binding introduced by an import declaration, assignment (x = ...), compound assignment.
  • no-inner-declarations: Reject block functions with legacy sloppy semantics; "both" also checks nested var declarations.
  • no-invalid-this: Reject this references outside any function-like, class method, or class-static-block context.
  • no-irregular-whitespace: Reject irregular whitespace characters in source.
  • no-iterator: Reject the legacy __iterator__ property, a SpiderMonkey-only extension predating ES2015 iterators.
  • no-labels: Reject labeled statements.
  • no-lone-blocks: Reject standalone { ... } blocks that introduce no lexical scope distinct from the surrounding block.
  • no-lonely-if: Reject if (cond) { if (...) { ... } }.
  • no-loop-func: Reject loop-created closures that capture bindings which can change between iterations.
  • no-loss-of-precision: Reject numeric literals whose source text cannot round-trip through Number.
  • no-magic-numbers: Reject inline numeric literals outside const initializer position.
  • no-misleading-character-class: Reject regex character classes that contain combined Unicode sequences (e.g. surrogate pairs).
  • no-mixed-operators: Reject mixing different operators from one precedence group (e.g. a && b || c, a + b * c) without parentheses.
  • no-multi-assign: Reject chained assignment such as a = b = 0.
  • no-multi-str: Reject backslash-newline multiline string literals.
  • no-negated-condition: Reject if (!cond) { ... } else { ... }, flip the branches.
  • no-nested-ternary: Reject ternary expressions nested in other ternaries.
  • no-new: Reject new expressions whose return value is not assigned or used.
  • no-new-func: Reject new Function(...) and Function(...) calls.
  • no-new-wrappers: Reject primitive wrapper constructors new String(...), new Number(...), new Boolean(...).
  • no-new-symbol: Reject new Symbol(...).
  • no-obj-calls: Reject calling global non-callable objects as functions.
  • no-object-constructor: Reject new Object() and Object() constructor calls.
  • no-octal: Reject legacy octal literals.
  • no-octal-escape: Reject octal escape sequences in string literals.
  • no-param-reassign: Reject writes to parameter bindings and, optionally, their properties.
  • no-plusplus: Reject ++ and -- operators.
  • no-promise-executor-return: Reject values returned by global Promise executors.
  • no-proto: Reject access to obj.__proto__; use Object.getPrototypeOf / Object.setPrototypeOf.
  • no-prototype-builtins: Reject obj.hasOwnProperty(key) and other direct Object.prototype builtins on user objects.
  • no-redeclare: Reject declaring the same binding more than once in the same scope.
  • no-regex-spaces: Reject more than one consecutive literal space in a regex.
  • no-restricted-imports: Reject static imports and re-exports selected by configured exact paths, gitignore-style groups, or regular expressions.
  • no-restricted-syntax: Reject only syntax matching configured TypeScript-Go AST selectors.
  • no-return-assign: Reject assignment expressions used as the operand of return , almost always a typo for ===.
  • no-script-url: Reject javascript: URLs in string literals, they execute their body as code on browser navigation.
  • no-self-assign: Reject x = x and destructuring forms.
  • no-self-compare: Reject comparing a value to itself.
  • no-sequences: Reject comma expressions outside the heads of for statements.
  • no-setter-return: Reject explicit return from a setter, setters’ return values are ignored.
  • no-shadow: Reject a variable declaration that shadows a same-name binding in an enclosing scope.
  • no-shadow-restricted-names: Reject redeclaring restricted globals.
  • no-sparse-arrays: Reject array literals with elision.
  • no-template-curly-in-string: Reject ${expr} inside ordinary single- or double-quoted strings, almost always a missing template-literal backtick.
  • no-this-before-super: Reject this references that precede the first super() call in a derived constructor.
  • no-throw-literal: Reject throwing non-Error operands.
  • no-undef-init: Reject initializing a variable to the literal undefined , declaring.
  • no-undefined: Reject use of the global undefined identifier.
  • no-unneeded-ternary: Reject cond ? true : false and similar ternaries.
  • no-unreachable: Reject statements that follow an unconditional return, throw, break, or continue in the same block.
  • no-unsafe-finally: Reject return and throw inside a finally block.
  • no-unsafe-negation: Reject !key in obj and !a instanceof B.
  • no-unsafe-optional-chaining: Reject member access or call expressions that chain off an optional chain.
  • no-unused-expressions: Reject expression statements with no observable effect, like a bare x;, a === b;, or a tagged template literal statement.
  • no-unused-labels: Reject labels that no break or continue statement references.
  • no-useless-assignment: Reject an assignment whose value is immediately overwritten by the very next statement.
  • no-useless-call: Reject unnecessary .call() / .apply() calls.
  • no-useless-catch: Reject catch (e) { throw e } patterns that only rethrow the caught error.
  • no-useless-computed-key: Reject computed property keys whose expression is a literal identifier.
  • no-useless-concat: Reject "a" + "b" and similar concatenations.
  • no-useless-constructor: Reject empty constructor bodies that add nothing over the implicit constructor.
  • no-useless-escape: Reject unnecessary escape sequences in strings and regex literals.
  • no-useless-rename: Reject { x: x } destructuring renames.
  • no-useless-return: Reject a bare return; whose only effect is to end a function body.
  • no-var: Reject var declarations.
  • no-with: Reject with (...) statements.

Prefer

Require

  • require-yield: Require generator functions to contain at least one yield.

Consistency

  • consistent-return: Reject functions where some return statements return a value and others do not.
  • sort-imports: Reject import specifiers within a single import declaration.
  • sort-keys: Reject object-literal property keys.

Validation

  • valid-typeof: Restrict the right-hand operand of typeof to the documented strings.

Other checks

  • curly: Require block statements for every if, else, while, for, and do body, reject the single-statement shorthand.
  • default-case: Require switch statements to include a default clause unless a // no default marker comment opts out.
  • default-case-last: Require the default clause of a switch statement.
  • camelcase: Reject identifier declarations that aren’t camelCase or PascalCase, snake_case bindings are flagged.
  • complexity: Reject function bodies whose cyclomatic complexity exceeds twenty (default ESLint threshold).
  • default-param-last: Reject (req, opt = 1, req2) and similar parameter lists.
  • dot-notation: Prefer dot access over bracket access.
  • eqeqeq: Require strict equality operators === / !== over == / !=.
  • for-direction: Reject for statements whose update clause moves the counter away from the termination condition.
  • getter-return: Require a get accessor’s body to return a value on every reachable exit.
  • grouped-accessor-pairs: Require the get and set accessors of a single property.
  • guard-for-in: Require the body of every for (key in obj) loop to begin with an if statement that can filter inherited keys.
  • id-length: Reject identifier names shorter than two characters.
  • init-declarations: Require every var / let declaration.
  • max-classes-per-file: Reject a source file that declares more than one class.
  • max-depth: Reject block-statement nesting deeper than four levels inside a function.
  • max-lines: Reject a source file whose total line count exceeds three hundred.
  • max-lines-per-function: Reject a function whose body spans more than fifty lines.
  • max-nested-callbacks: Reject callback nesting deeper than ten inside a single function.
  • max-params: Reject function declarations whose parameter list grows beyond three.
  • max-statements: Reject function bodies whose statement count exceeds ten.
  • object-shorthand: Reject { foo: foo } and similar object-literal shorthand candidates.
  • operator-assignment: Prefer compound assignment over the long form.
  • radix: Require an explicit radix argument for parseInt(str, radix).
  • use-isnan: Require Number.isNaN / isNaN for NaN checks.
  • vars-on-top: Require var declarations to be hoisted to the top of their scope by hand, mirroring how the engine treats them.
  • yoda: Reject Yoda-style comparisons ; use if (x === 42).

Rules

curly

Require block statements for every if, else, while, for, and do body, reject the single-statement shorthand.

Example:

const flag: boolean = Math.random() > 0.5; // reports: curly (error) if (flag) console.log("if");

default-case

Require switch statements to include a default clause.

A switch without default silently drops every discriminant value that no case label matched.

The rule forces the catch-all branch to be written out so unhandled cases become an intentional decision instead of a hidden fall-through. An empty switch (switch (x) {}) is skipped, matching ESLint. A switch may opt out by placing a marker comment whose trimmed text matches commentPattern (default /^no default$/i, for example // no default) as the last comment after its final clause.

Example:

function classify(kind: string): string { // reports: default-case (error) switch (kind) { case "a": return "letter-a"; case "b": return "letter-b"; } return "unknown"; } function classifyMarked(kind: string): string { switch (kind) { case "a": return "letter-a"; // no default } return "unknown"; }

Options:

  • commentPattern?: string

    Regular expression the opt-out marker comment must match. Replaces the default /^no default$/i pattern entirely.

default-case-last

Require the default clause of a switch statement to appear after every explicit case label.

Placing default ahead of a case reverses the visual order of the labels and changes the fall-through path, running default and then falling into the next case is almost always a misordering rather than intent.

Example:

function classify(kind: string): string { switch (kind) { // reports: default-case-last (error) default: return "unknown"; case "a": return "letter-a"; case "b": return "letter-b"; } }

camelcase

Reject identifier declarations that aren’t camelCase or PascalCase, snake_case bindings are flagged.

Example:

// reports: camelcase (error) const snake_value: number = 1; const camelValue: number = 2; const PascalValue: number = 3; const _private: number = 4; const MAX_VALUE: number = 5; function goodName(): void {} // reports: camelcase (error) function bad_name(): void {} class GoodClass {} // reports: camelcase (error) class bad_class {} function take(good: number, _ignored: number): void { void good; }

complexity

Reject function bodies whose cyclomatic complexity exceeds twenty (default ESLint threshold).

Example:

// reports: complexity (error) function tooComplex(input: number): string { // Base complexity is 1. The body below adds 21 more branching points, // enough to exceed ESLint's default limit of 20. if (input === 0) return "zero"; if (input === 1) return "one"; if (input === 2) return "two"; if (input === 3) return "three"; if (input === 4) return "four"; if (input === 5) return "five"; if (input === 6) return "six"; if (input === 7) return "seven"; if (input === 8) return "eight"; if (input === 9) return "nine"; if (input === 10) return "ten"; if (input === 11) return "eleven"; if (input === 12) return "twelve"; if (input === 13) return "thirteen"; if (input === 14) return "fourteen"; if (input === 15) return "fifteen"; if (input === 16) return "sixteen"; if (input === 17) return "seventeen"; if (input === 18) return "eighteen"; if (input === 19) return "nineteen"; if (input === 20) return "twenty"; return "many"; }

consistent-return

Reject functions where some return statements return a value and others (explicit bare return; or implicit fall-through) do not.

Example:

// reports: consistent-return (error) function mixed(flag: boolean): number | undefined { if (flag) { return 1; } return; }

default-param-last

Reject (req, opt = 1, req2) and similar parameter lists where a required parameter follows an optional or default-valued one.

The call site cannot omit the trailing required parameter, so the optional becomes positionally required too, almost always an accidental ordering.

Example:

function bad( // reports: default-param-last (error) a?: number, b: number, ): number { return (a ?? 0) + b; } function trailing(b: number, a?: number): number { return (a ?? 0) + b; }

dot-notation

Prefer dot access (obj.value) over bracket access (obj["value"]) when the string key is a valid JavaScript identifier.

Bracket access remains accepted for dynamic keys or keys containing characters that cannot appear in an identifier.

The autofix inserts a space after a bare decimal-integer receiver (5["toString"] becomes 5 .toString) so the dot is not lexed as the number’s decimal point.

Two cases are reported without an autofix and offer the rewrite as an editor suggestion instead: a comment inside the bracket span (applying the suggestion deletes that comment), and a reserved-word key such as box["class"] (valid on modern targets, but bracket access is kept by default because minifiers and older engines can break on it).

Example:

const box = { name: "ttsc", "not-valid-key": "kept" }; // reports: dot-notation (error) const value = box["name"]; const kept = box["not-valid-key"];

eqeqeq

Require strict equality operators === / !== over == / !=.

Loose equality performs implicit type coercion that frequently hides bugs.

The operator is autofixed only when both operands are provably the same type (two same-kind literals, or a typeof comparison). Otherwise tightening the comparison can change what the program computes, so the rewrite is offered as an editor suggestion the author opts into rather than being applied by ttsc fix.

Example:

function f(left: number | string, right: number | string) { // reports: eqeqeq (error) return left == right; }

for-direction

Reject for statements whose update clause moves the counter away from the termination condition, such as for (let i = 0; i < 10; i--). Such loops either never run or never terminate.

Example:

// reports: for-direction (error) for (let i = 0; i < 10; i--) {}

getter-return

Require a get accessor’s body to return a value on every reachable exit.

A getter that falls through returns undefined to the caller, which is almost never the intent and turns into a silent bug that only surfaces when the property is finally read.

Example:

class MissingReturn { // reports: getter-return (error) get value(): number { const x = 1 + 1; } }

grouped-accessor-pairs

Require the get and set accessors of a single property to be declared next to each other, in either a class body or an object literal.

When the read and write halves of a property are split apart by unrelated members, a reader scanning the declaration has to chase the pair across the body, and patches to one half are easy to make without noticing the other.

The optional order argument ("anyOrder", the default, "getBeforeSet", or "setBeforeGet") also fixes the relative order of an already adjacent pair.

Example:

class SplitAccessor { private state = 0; get value(): number { return this.state; } other(): void { this.state += 1; } // reports: grouped-accessor-pairs (error) set value(next: number) { this.state = next; } }

guard-for-in

Require the body of every for (key in obj) loop to begin with an if statement, so inherited keys walked in from the prototype chain can be filtered out. Following ESLint, the check is purely structural: it never inspects what the if tests, so any guard satisfies it — Object.hasOwn(obj, key), obj.hasOwnProperty(key), or an arbitrary predicate.

The body is accepted when it is an empty statement, an if statement, an empty block, a block whose only statement is an if, or a block whose leading if skips inherited keys with continue (for example if (!Object.hasOwn(obj, key)) continue;). Any other body is reported.

Without a guard the loop processes every enumerable name on the prototype chain, including monkey-patches someone else attached to Object.prototype, so an unguarded body silently leaks work onto inherited entries.

Example:

function dumpAll(obj: Record<string, unknown>): void { // reports: guard-for-in (error) for (const key in obj) { console.log(key, obj[key]); } }

id-length

Reject identifier names shorter than two characters.

Example:

// reports: id-length (error) const a: number = 1; const ab: number = 2; const longer: number = 3; // reports: id-length (error) function f(): void {} function go(): void {} // reports: id-length (error) class C {} class Foo {} function take( // reports: id-length (error) x: number, yy: number, longParam: number, ): void { void yy; void longParam; }

init-declarations

Require every var / let declaration to be initialized at its declaration site.

Example:

// reports: init-declarations (error) let pending: number | undefined; pending = 1;

max-classes-per-file

Reject a source file that declares more than one class.

Example:

class First { value(): number { return 1; } } // reports: max-classes-per-file (error) class Second { value(): number { return 2; } }

max-depth

Reject block-statement nesting deeper than four levels inside a function.

Example:

function deep(values: ReadonlyArray<number>): number { let total = 0; if (values.length > 0) { for (const value of values) { while (total < 100) { if (value > 0) { // reports: max-depth (error) if (value % 2 === 0) { total += value; } } total += 1; } } } return total; }

max-lines

Reject a source file whose total line count exceeds three hundred.

Example:

void 290; void 291; void 292; void 293; void 294; void 295; void 296; void 297; void 298; void 299; // reports: max-lines (error) void 301; void 302; void 303; void 304; void 305;

max-lines-per-function

Reject a function whose body spans more than fifty lines.

Example:

// With max set to 10. // reports: max-lines-per-function (error) function longBody(): number { let total = 0; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; total += 1; return total; }

max-nested-callbacks

Reject callback nesting deeper than ten inside a single function.

Example:

schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => schedule(() => // reports: max-nested-callbacks (error) schedule(() => { void 0; }), ), ), ), ), ), ), ), ), ), );

max-params

Reject function declarations whose parameter list grows beyond three.

Long parameter lists are hard to read at the call site because positional arguments lose their names; folding them into an options object recovers the names and lets callers pass a subset.

Every function-like declaration is checked: function declarations, function expressions, arrow functions, methods, accessors, and constructors.

The threshold is fixed at three to match the ESLint default; rule options are deferred.

Example:

// reports: max-params (error) function four(a: number, b: number, c: number, d: number): number { return a + b + c + d; }

max-statements

Reject function bodies whose statement count exceeds ten.

Example:

// reports: max-statements (error) function eleven(): number { const a = 1; const b = 2; const c = 3; const d = 4; const e = 5; const f = 6; const g = 7; const h = 8; const i = 9; const j = 10; return a + b + c + d + e + f + g + h + i + j; }

no-alert

Reject calls to alert, confirm, and prompt.

These browser dialogs block the main thread and are almost always debugging leftovers or placeholders for a proper UI component.

Example:

// reports: no-alert (error) alert("hi");

no-array-constructor

Reject Array(...) and new Array(...) constructor calls in favor of array literals.

Array(n) and [n] behave differently for a single numeric argument, and the array literal is uniformly clearer.

Example:

// reports: no-array-constructor (error) const a = new Array();

no-async-promise-executor

Reject new Promise(async (resolve, reject) => { ... }).

Promises thrown asynchronously inside the executor are dropped silently because the constructor has already returned the outer Promise. Use a regular function and call reject explicitly.

Example:

// reports: no-async-promise-executor (error) new Promise(async (resolve) => { resolve(1); });

no-await-in-loop

Reject explicit await expressions and implicit awaits from for await ... of or await using when they execute in a repeated loop position. A for initializer and the right-hand side of for...in or for...of run once and are allowed; loop tests, updates, and bodies repeat and are checked.

The loop runs strictly serially because each iteration blocks on the previous one’s microtask hop; when the operations are independent the equivalent Promise.all([...]) is dramatically faster.

The rule treats a for await ... of as a boundary for its own body because asynchronous iteration is intentional. The same statement is reported when nested in an ordinary loop, where creating and consuming the async iterator becomes part of each outer iteration.

Example:

async function inForLoop(): Promise<number> { let total = 0; for (let i = 0; i < 3; i++) { // reports: no-await-in-loop (error) total += await getPromise(); } return total; }

no-bitwise

Reject bitwise operators (&, |, ^, ~, <<, >>, >>>).

Bitwise operators are almost always typos for the logical operators (&&, ||); enable when the codebase has no legitimate bit-twiddling.

Example:

function f(a: number, b: number) { // reports: no-bitwise (error) return a & b; }

no-caller

Reject arguments.caller and arguments.callee, both deprecated properties forbidden in strict mode.

They defeat engine optimizations and break under ES modules, where strict mode is implicit.

Example:

function f() { // reports: no-caller (error) return arguments.callee; }

no-case-declarations

Reject lexical declarations (let, const, class, function) inside case or default clauses without their own block, since the declaration shares the whole switch scope and leaks into sibling clauses.

Wrap the case body in { ... } to introduce a fresh block.

Example:

function f(x: number) { switch (x) { case 1: // reports: no-case-declarations (error) let y = 1; return y; } return 0; }

no-class-assign

Reject every write to a binding introduced by a class declaration or named class expression.

The rule follows lexical binding identity rather than matching names. Same-spelled parameter, catch, block, and sibling bindings remain independent, while direct and compound assignments, updates, destructuring targets, and for-in/for-of targets all report writes to the actual class binding.

Example:

class A {} // reports: no-class-assign (error) A = class Replacement {};

no-compare-neg-zero

Reject comparisons against -0 (x === -0, x < -0, etc.).

=== treats +0 and -0 as equal, so the comparison never distinguishes them; use Object.is(x, -0) when the sign of zero actually matters.

Example:

function f(x: number) { // reports: no-compare-neg-zero (error) return x === -0; }

no-cond-assign

Reject assignment expressions inside conditions, such as if (x = y), almost always a typo for == / ===.

Example:

let a = 0; let b = 1; // reports: no-cond-assign (error) if ((a = b)) { console.log(a); }

no-console

Reject calls to console.*.

Typically configured as "warning" so leftover logging stays visible without breaking the build.

Example:

// reports: no-console (error) console.log("hi");

no-constant-condition

Reject conditions whose value can be determined statically, such as while (true) or if (false), in if, while, do/while, for, and ternary expressions.

The default checkLoops configuration still permits intentional infinite loops in a few forms; see upstream for the matrix.

Example:

// reports: no-constant-condition (error) if (1) { console.log("always"); }

no-continue

Reject continue statements.

Stylistic policy preferring early returns or restructured loops over continue.

Example:

for (let i = 0; i < 3; i++) { // reports: no-continue (error) if (i === 1) continue; console.log(i); }

no-constructor-return

Reject return X; (with a value) inside a class constructor.

The returned value is ignored when the constructor is invoked with new unless it happens to be an object; relying on that behavior is always a misunderstanding of the constructor protocol.

Example:

class Returning { value: number; constructor(initial: number) { this.value = initial; // reports: no-constructor-return (error) return { handled: true }; } }

no-control-regex

Reject ASCII control characters (\x00-\x1F) inside regular expression literals and RegExp strings.

They render invisibly in source and almost always indicate an accidental paste or a missed \t / \n escape.

Example:

// reports: no-control-regex (error) const r = /\x1f/;

no-debugger

Reject debugger statements.

Typically configured as "error" so accidental debugger leftovers fail CI.

Example:

function f(): void { // reports: no-debugger (error) debugger; } f();

no-delete-var

Reject delete applied to plain variable bindings (delete x).

The operation is forbidden in strict mode (and therefore in ES modules) and never has the intended effect on let / const / var declarations.

Example:

let value = 1; // reports: no-delete-var (error) delete value;

no-dupe-args

Reject function f(a, a) and similar parameter lists that declare the same name twice.

The function cannot bind both arguments and fails in strict mode.

Example:

// reports: no-dupe-args (error) function f(a: number, b: number, a: number) { return a + b; } f(1, 2, 3);

no-dupe-class-members

Reject two declarations of the same member on a single class. The later declaration silently overwrites the earlier one at runtime; the syntax permits it but the result is never what the author intended.

A getter and a setter for the same property coexist; an instance member and a static member with the same name coexist.

Example:

class Methods { run(): number { return 1; } // reports: no-dupe-class-members (error) run(): number { return 2; } }

no-dupe-else-if

Reject if (a) {} else if (a) {}, the second branch is unreachable because the first condition already handled it.

Example:

function chooseCacheMode(isReady: boolean, isCached: boolean) { if (isReady) { return "ready"; } else if (isCached) { return "cached"; } // reports: no-dupe-else-if (error) else if (isReady) { return "still-ready"; } return "cold"; }

no-dupe-keys

Reject { a: 1, a: 2 }, duplicate property keys in an object literal silently overwrite earlier values. A computed key is resolved to its static literal value, so ["a"] duplicates the identifier key a; a non-constant computed key such as [f()] or [x] is not statically known and is never treated as a duplicate.

Example:

const o = { a: 1, // reports: no-dupe-keys (error) a: 2, // reports: no-dupe-keys (error) ["a"]: 3, };

no-duplicate-case

Reject the same case label appearing twice in a switch, later duplicates are unreachable.

Example:

function f(x: number) { switch (x) { case 1: return "a"; // reports: no-duplicate-case (error) case 1: return "b"; } return ""; }

no-duplicate-imports

Reject an import declaration whose module specifier already appeared above when the two declarations could be merged into one legal declaration. Same-module pairs TypeScript cannot consolidate, such as named next to namespace bindings or a type-only default next to type-only named bindings, are not duplicates.

Options:

  • allowSeparateTypeImports?: boolean

    Keep clause-level import type declarations out of the comparison with value imports of the same module, so one runtime import plus one type-only import may coexist. Inline type specifiers such as import { type Foo } stay on the value side because the whole import clause is not type-only. Default: false.

  • includeExports?: boolean

    Also treat export … from declarations of an already imported or re-exported module as duplicates when the declarations could be merged. Default: false.

Example:

import { first } from "some-module"; // reports: no-duplicate-imports (error) import { second } from "some-module"; // Not duplicates: named and namespace bindings cannot share one declaration. import { named } from "unmergeable-namespace"; import * as namespace from "unmergeable-namespace";

no-else-return

Reject an else block whose preceding if branch already ends in a return, so its body can be flattened into the surrounding scope for one less level of nesting. Only return counts as leaving the branch; throw, break, and continue keep the else in place.

Options:

  • allowElseIf?: boolean

    When true (the default), a return followed by an else if chain that does not end in a plain else is left alone, so the common early-return chain stays valid. Set it to false to also reject the else if. Default: true.

Example:

function describe(kind: string): string { if (kind === "a") { return "letter-a"; // reports: no-else-return (error) } else { return "other"; } } // Allowed by default (`allowElseIf: true`): a `return` + `else if` chain. function classifySign(n: number): string { if (n > 0) { return "positive"; } else if (n < 0) { return "negative"; } return "zero"; }

no-empty

Reject empty blocks and switches that have no interior comment. Empty catch clauses are reported by default; set allowEmptyCatch to true when silently ignoring caught errors is an established project convention.

Options:

  • allowEmptyCatch?: boolean

    Accept uncommented empty catch clauses while continuing to report every other uncommented empty block. Default: false.

Example:

function f(x: number) { // reports: no-empty (error) if (x === 0) { } }
ttsc.lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint"; export default { rules: { "no-empty": ["error", { allowEmptyCatch: true }], }, } satisfies ITtscLintConfig;

no-empty-character-class

Reject non-negated empty regex character classes ([]), including nested classes in Unicode Sets (v) mode. An empty class never matches a character, so its containing alternative cannot match through that class; the negated form [^] matches any character and is allowed.

Example:

// reports: no-empty-character-class (error) const r = /[]/;

no-empty-function

Reject empty function bodies that have no interior comment. The allow option accepts the same function, arrow, generator, method, accessor, constructor, async, decorator, and override categories as ESLint. TypeScript parameter-property constructors are always accepted because their parameters initialize fields.

Options:

  • allow?: TtscLintCoreNoEmptyFunctionAllow[]

    Accept the selected categories: functions, arrowFunctions, generatorFunctions, methods, generatorMethods, getters, setters, constructors, asyncFunctions, asyncMethods, privateConstructors, protectedConstructors, decoratedFunctions, and overrideMethods. Default: [].

Example:

// reports: no-empty-function (error) function f(): void {} f();
ttsc.lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint"; export default { rules: { "no-empty-function": [ "error", { allow: ["constructors", "overrideMethods"] }, ], }, } satisfies ITtscLintConfig;

no-empty-named-blocks

Reject empty named import or export clauses, import {} from "x", import name, {} from "x", and export {}, which bind nothing.

The empty-only import shape leaves just the side-effect load and is better written as import "x"; the default-plus-empty form should drop the empty clause.

And a bare export {} either restates module-ness redundantly or marks an otherwise non-module file in a way that has cleaner alternatives.

The stricter sibling rule typescript/no-useless-empty-export fires only when another module-syntax statement is already present.

Example:

// reports: no-empty-named-blocks (error) import "x";

no-empty-pattern

Reject empty destructuring patterns (const {} = obj, function f([]) {}), which bind nothing and are usually mid-edit typos.

Example:

// reports: no-empty-pattern (error) function f({}: { a?: number }): void {} f({ a: 1 });

no-empty-static-block

Reject empty static {} class initialization blocks unless an interior comment documents why the block is intentionally empty.

Example:

class Holder { // reports: no-empty-static-block (error) static {} }

no-eq-null

Reject loose null comparisons (x == null).

Use x === null or the explicit x === null || x === undefined.

Pairs with eqeqeq but kept separate so the loose null shortcut can be allowed under "smart"-style eqeqeq exceptions.

Example:

function f(value: string | null) { // reports: no-eq-null (error) return value == null; }

no-eval

Reject eval(...) and indirect eval calls, almost always a security or correctness bug.

Example:

// reports: no-eval (error) eval("1");

no-ex-assign

Reject reassigning the parameter of a catch clause (catch (e) { e = ... }), which loses the original error reference.

Example:

try { throw new Error("x"); } catch (e) { // reports: no-ex-assign (error) e = "boom"; console.log(e); }

no-extend-native

Reject extending a built-in prototype such as Array.prototype.foo = bar. Every shape that adds to a native prototype is flagged: dotted (X.prototype.y = ...) and computed (X.prototype["y"] = ...) member assignment, plus Object.defineProperty / Object.defineProperties whose target is a native prototype. Assigning to Object.foo = ... (a static property) is left alone. The exceptions option lists builtins to allow.

Example:

// reports: no-extend-native (error) Array.prototype.firstItem = 1; // reports: no-extend-native (error) String.prototype.upper = function (): void {}; // reports: no-extend-native (error) Array.prototype["lastItem"] = 2; // reports: no-extend-native (error) Object.defineProperty(Number.prototype, "half", { value: 3 }); Object.debugLabel = 1;

Allow specific builtins with the exceptions option:

{ "rules": { "no-extend-native": ["error", { "exceptions": ["Array"] }] } }

no-extra-bind

Reject .bind(thisArg) on an arrow function or on a regular function whose own scope never reads this. A nested arrow inherits the enclosing regular function’s receiver and therefore counts as a use, while a nested regular function owns a separate receiver. Calls with arguments after thisArg and calls that spread their arguments remain unchanged because they can perform partial application.

Dot, static computed, and optional member/call forms are recognized. The autofix removes the bind syntax only when evaluating thisArg is side-effect-free and no comment lies inside the discarded ranges. A comment in those ranges downgrades the removal to an editor suggestion (applying it deletes the comment), while an effectful thisArg stays diagnostic-only because removing the bind would also remove an evaluation the program performs.

Example:

// reports: no-extra-bind (error) const f = (() => 1).bind({}); const partial = ((value: number) => value).bind(null, 1);

no-extra-boolean-cast

Reject redundant boolean casts such as !!Boolean(x), if (Boolean(x)), or Boolean(!!x).

The autofix splices the inner expression over the whole cast, parenthesizing it where precedence requires. When a comment sits inside the replaced span the splice would delete it, so the rewrite is offered as an editor suggestion instead of being applied automatically.

Example:

function f(isReady: boolean) { // reports: no-extra-boolean-cast (error) if (!!isReady) { return 1; } return 0; }

no-fallthrough

Reject switch cases that can reach the next label without an intentional // falls through comment.

Reachability follows statement completion, matching the ESLint rule: a case whose every path ends in break, continue, return, or throw (composed through blocks, if/else, loops, labeled statements, and try/catch/finally) needs no break. A catch contributes only when its try block has a reachable throw path, so a bare or literal return does not enter it while an explicit throw or an evaluated identifier, member access, call, or construction can. Parenthesized primitive literals are folded for loop tests, including zero and nonzero BigInt literals in every radix with separators, but unary expressions remain dynamic. Nested functions, class field initializers, and static blocks keep separate code paths, so their internal completions do not terminate the enclosing case. An intentional fallthrough is marked by the last comment before the next label (or before the closing brace when the case body is a single block) matching /falls?\s?through/i; directive comments such as // eslint-disable-next-line ... never count as markers.

Example:

function f(x: number) { switch (x) { case 1: console.log("one"); case 2: // reports: no-fallthrough (error) console.log("two"); // falls through case 3: if (x > 1) { return; } else { throw new Error("stop"); } case 4: // no report: case 3 terminates on every path console.log("four"); break; } }

Options:

  • commentPattern?: string

    Regular expression an intentional-fallthrough comment must match. Replaces the default /falls?\s?through/i pattern entirely.

  • allowEmptyCase?: boolean

    Allow an empty case separated from the next label by blank lines. Adjacent labels (case 0: case 1:) are always allowed. Default: false.

  • reportUnusedFallthroughComment?: boolean

    Report a fallthrough marker on a case that cannot fall through (for example after a break). Default: false.

no-func-assign

Reject writes to bindings introduced by function declarations and named function expressions.

The rule follows lexical binding identity, so same-spelled parameters, block variables, catch variables, and sibling bindings remain independent. Direct and compound assignments, updates, destructuring targets, and for-in/of targets all count as writes.

Example:

function g() { return 1; } // reports: no-func-assign (error) g = function () { return 2; };

no-implicit-coercion

Reject common implicit-coercion idioms (!!x, +x, "" + x) in favor of the explicit Boolean(x) / Number(x) / String(x) conversions.

The explicit forms are more readable and avoid surprise around primitive edge cases.

Example:

const value: unknown = "ready"; // reports: no-implicit-coercion (error) const asBool = !!value;

no-import-assign

Reject assignments, updates, destructuring writes, and for-in / for-of targets that resolve to an imported binding. Same-spelled local shadows remain independent because the rule follows binding identity rather than identifier text.

For namespace imports, the rule also rejects direct member assignment, update, deletion, and the standard mutating Object / Reflect calls such as Object.assign(ns, value). Objects exported below a namespace member remain mutable (ns.value.property = next).

Imported bindings are read-only at runtime; mutating them either throws under strict mode or silently desynchronises the module’s view of its own exports.

Example:

import { x } from "y"; import * as ns from "y"; // reports: no-import-assign (error) x = 5; // reports: no-import-assign (error) Object.assign(ns, { x: 5 });

no-inner-declarations

By default, reject function declarations in nested blocks when the enclosing code is sloppy and legacy function-hoisting semantics apply. Strict scripts and function bodies, ECMAScript modules, and class code use ES2015 block-scoped functions and are allowed by default.

Use the canonical "both" positional mode to check nested var declarations too. Set blockScopedFunctions to "disallow" when strict block functions should be rejected as a style policy:

const config = { rules: { "no-inner-declarations": [ "error", "both", { blockScopedFunctions: "disallow" }, ], }, };

Example:

function outer() { if (1) { // reports: no-inner-declarations (error) function inner() {} inner(); } } outer();

no-invalid-this

Reject this references outside any function-like, class method, or class-static-block context.

Example:

// reports: no-invalid-this (error) const value = this;

no-irregular-whitespace

Reject irregular whitespace characters (zero-width space, non-breaking space, etc.) in source, typically copy-paste artifacts from rich-text editors.

Example:

// reports: no-irregular-whitespace (error) const a = 1;

no-iterator

Reject the legacy __iterator__ property, a SpiderMonkey-only extension predating ES2015 iterators.

Use Symbol.iterator or a generator.

Example:

const legacyObject = {}; // reports: no-iterator (error) legacyObject.__iterator__ = function* () {};

no-labels

Reject labeled statements (outer: for (...) { break outer; }).

Labels obscure control flow; prefer extracting the inner loop into a function and using return, or refactoring with a flag variable.

Example:

// reports: no-labels (error) outer: for (let i = 0; i < 3; i++) { break outer; }

no-lone-blocks

Reject standalone { ... } blocks that introduce no lexical scope distinct from the surrounding block.

Blocks that actually declare let, const, class, or function (in strict mode) are exempt, since those declarations need the inner scope.

Example:

// reports: no-lone-blocks (error) { console.log("hi"); }

no-lonely-if

Reject if (cond) { if (...) { ... } } where the inner if is the only statement in an else, prefer else if.

Example:

function chooseMode(isPrimary: boolean, isFallback: boolean) { if (isPrimary) { return "primary"; } else { // reports: no-lonely-if (error) if (isFallback) { return "fallback"; } } return "none"; }

no-loop-func

Reject loop-created closures that capture bindings which can change between iterations.

The rule follows binding identity and write locations instead of rejecting every function beneath a loop. A shared var counter or an outer binding written after the loop starts is unsafe. Constants, per-iteration let bindings, functions with no upper-scope references, and unreferenced synchronous IIFEs are safe.

Example:

function inForLoop(): void { for (var i = 0; i < 3; i++) { // reports: no-loop-func (error) function inner() { return i; } inner(); } } for (let i = 0; i < 3; i++) { const safe = () => i; safe(); }

no-loss-of-precision

Reject decimal, binary, octal, and hexadecimal numeric literals whose requested significant digits cannot survive Number conversion. This includes fractions, exponents, numeric separators, underflow, and overflow. BigInt literals are excluded.

Example:

// reports: no-loss-of-precision (error) const big = 9007199254740993; // reports: no-loss-of-precision (error) const roundedHex = 0x20000000000001;

no-magic-numbers

Reject inline numeric literals outside const initializer position. 0, 1, -1, array indices, and enum values are exempt.

Example:

const items = [zero, one]; const first = items[0]; // reports: no-magic-numbers (error) const total = SECONDS_PER_MINUTE * 60;

no-misleading-character-class

Reject regex character classes that contain combined Unicode sequences (e.g. surrogate pairs) which most readers will not realize represent multiple code units.

Example:

// reports: no-misleading-character-class (error) const r = /[👍]/;

no-mixed-operators

Reject mixing two different operators from one precedence group without parentheses when the operators do not share a precedence. The groups mirror ESLint’s defaults — arithmetic (+ - * / % **), bitwise (& | ^ ~ << >> >>>), comparison (== != === !== > >= < <=), logical (&& ||), and relational (in instanceof) — and the rule fires only when both operators fall in the same group.

The famous case is a && b || c: readers expect left-to-right grouping but the parser sees (a && b) || c because && binds tighter than ||. Arithmetic mixes such as a + b * c are reported for the same reason. Cross-group pairs (a | b && c) and same-precedence pairs (a + b - c) are left alone.

Wrapping the inner sub-expression in parens suppresses the report. The groups and allowSamePrecedence options tune the groups and the same-precedence allowance exactly as they do in ESLint.

Example:

// reports: no-mixed-operators (error) const m1 = a && b || c; // ok — the grouping is explicit const m2 = (a && b) || c;

no-multi-assign

Reject chained assignment such as a = b = 0, which obscures intent and surprises readers who expect comparison.

Example:

let left: number; let right: number; // reports: no-multi-assign (error) left = right = 1;

no-multi-str

Reject backslash-newline multiline string literals; use template literals instead.

Example:

const s: string = // reports: no-multi-str (error) "line1 \ line2";

no-negated-condition

Reject if (!cond) { ... } else { ... }, flip the branches so the positive condition reads first.

Example:

function f(isReady: boolean) { // reports: no-negated-condition (error) if (!isReady) { return 1; } else { return 2; } }

no-nested-ternary

Reject ternary expressions nested in other ternaries (a ? b : c ? d : e), which are hard to read at a glance.

Example:

function label(value: number): string { // reports: no-nested-ternary (error) return value === 0 ? "zero" : value > 0 ? "positive" : "negative"; }

no-new

Reject new expressions whose return value is not assigned or used, the object is created only for its constructor side effects.

Example:

class Thing {} // reports: no-new (error) new Thing();

no-new-func

Reject new Function(...) and Function(...) calls, which effectively evaluate a string and have the same risks as eval.

Example:

// reports: no-new-func (error) const f = new Function("a", "return a");

no-new-wrappers

Reject primitive wrapper constructors new String(...), new Number(...), new Boolean(...).

The resulting objects compare unequal to their primitive counterparts.

Example:

// reports: no-new-wrappers (error) const s = new String("a");

no-new-symbol

Reject new Symbol(...). Symbol is a function but not a constructor; calling it with new throws a TypeError at runtime.

The upstream rule was renamed no-new-native-nonconstructor; the legacy name remains the more readable pointer for this specific Symbol check.

Example:

// reports: no-new-symbol (error) const bad = new Symbol("desc");

no-obj-calls

Reject calling global non-callable objects as functions, such as Math() or JSON().

Example:

// reports: no-obj-calls (error) Math();

no-object-constructor

Reject new Object() and Object() constructor calls; use an object literal {} instead.

Example:

// reports: no-object-constructor (error) const o = new Object();

no-octal

Reject legacy octal literals (0123).

Use the 0o123 prefix when an octal literal is actually intended.

Example:

// reports: no-octal (error) const n = 010;

no-octal-escape

Reject octal escape sequences in string literals ("\251", "\07").

Deprecated and forbidden in strict mode; use Unicode (©) or hex (\xA9) escapes.

Example:

// reports: no-octal-escape (error) const s: string = "\251";

no-param-reassign

Reject writes to a function parameter binding, including destructured parameters and writes from nested closures. Shadowing locals are independent bindings and are not reported.

Property mutations (param.foo = ...) are accepted by default. Set props to true to report them. With props: true, ignorePropertyModificationsFor accepts exact parameter names and ignorePropertyModificationsForRegex accepts regular-expression strings for parameters whose property writes should remain allowed.

Example:

function reassignSimple(x: number): number { // reports: no-param-reassign (error) x = 1; return x; } function mutateDraft(draft: { value: number }): void { // reports with { props: true }: no-param-reassign (error) draft.value = 1; }

no-plusplus

Reject ++ and -- operators.

Prefer += 1 / -= 1 to keep statements expression-only and avoid ASI surprises.

Example:

let i = 0; // reports: no-plusplus (error) i++;

no-promise-executor-return

Reject values returned by the global Promise constructor’s executor because the constructor ignores them. The rule checks concise arrow bodies and every explicit value return in block arrows or function expressions without crossing nested function boundaries. A locally shadowed Promise constructor is not checked.

Set allowVoid to true to accept concise void expression bodies and explicit return void expression statements. It defaults to false.

Example:

// reports: no-promise-executor-return (error) new Promise((resolve) => resolve(1)); new Promise(() => { // reports: no-promise-executor-return (error) return 1; });
ttsc.lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint"; export default { rules: { "no-promise-executor-return": ["error", { allowVoid: true }], }, } satisfies ITtscLintConfig;

no-proto

Reject access to obj.__proto__; use Object.getPrototypeOf / Object.setPrototypeOf.

Example:

const legacyObject = {}; // reports: no-proto (error) const prototype = legacyObject.__proto__;

no-prototype-builtins

Reject obj.hasOwnProperty(key) and other direct Object.prototype builtins on user objects, since the property may be shadowed.

Use Object.prototype.hasOwnProperty.call(obj, key) or Object.hasOwn.

Example:

const user = { id: 1 }; // reports: no-prototype-builtins (error) user.hasOwnProperty("id");

no-redeclare

Reject declaring the same binding more than once in the same scope (var x = 1; var x = 2;, two function foo() declarations side by side, or a parameter rebound by a later var in the body).

The second declaration silently overwrites the first; shadowing the binding in a nested scope is left alone.

Example:

var sample: number = 1; // reports: no-redeclare (error) var sample: number = 2;

no-regex-spaces

Reject more than one consecutive literal space in a regex; use {N} quantifiers for clarity.

Example:

// reports: no-regex-spaces (error) const r = /a b/;

no-restricted-imports

Reject static imports and re-exports selected by user-configured exact paths, gitignore-style groups, or regular expressions. A severity-only setting, an empty paths list, and an empty patterns list are all no-ops; the rule never infers a project denylist.

Exact-path and structured-pattern entries can restrict or allow selected source names, append a custom message, and permit type-only imports and re-exports. Pattern entries also support case-sensitive matching and source-name regular expressions.

Options:

  • paths?: (string | { name, message?, importNames?, allowImportNames?, allowTypeImports? })[]

  • patterns?: string[] | ({ group } | { regex })[]

    Structured pattern entries also accept message, caseSensitive, importNames, allowImportNames, importNamePattern, allowImportNamePattern, and allowTypeImports. Each entry uses either deny-name controls or allow-name controls, not both.

Example:

// reports: no-restricted-imports (error) import legacy from "legacy-package"; // reports: no-restricted-imports (error) export { privateApi } from "internal/private";
ttsc.lint.config.ts
import type { ITtscLintConfig } from "@ttsc/lint"; export default { rules: { "no-restricted-imports": [ "error", { paths: [ { name: "legacy-package", importNames: ["default"], message: "Use the supported package.", allowTypeImports: true, }, ], patterns: [ { group: ["internal/*", "!internal/public"], allowImportNames: ["publicApi"], }, ], }, ], }, } satisfies ITtscLintConfig;

no-restricted-syntax

Reject only syntax matching selectors supplied after the severity. The rule has no default denylist: "no-restricted-syntax": "error" and ["error"] are both silent.

export default { rules: { "no-restricted-syntax": [ "error", "WithStatement", { selector: "CallExpression[callee.name='eval']", message: "Do not evaluate source text.", }, "TSAsExpression > Literal.expression", ], }, };

The selector grammar follows esquery and supports:

  • native TypeScript-Go node kinds without the Kind prefix, plus *;
  • attribute presence, equality/inequality, numeric comparisons, type(...), nested paths, and /pattern/imsu regular expressions;
  • child (>), descendant (space), following-sibling (~), and adjacent (+) combinators;
  • :not, :is/:matches, :has, :first-child, :last-child, :nth-child(n), :nth-last-child(n), and ! subjects;
  • the :statement, :expression, :declaration, :function, and :pattern classes; and
  • field selectors such as FunctionDeclaration > Identifier.id.

Native names are case-insensitive. Supported ESTree aliases are Program, ArrowFunctionExpression, ObjectExpression, ArrayExpression, ObjectPattern, ArrayPattern, VariableDeclarator, MemberExpression, AssignmentExpression, LogicalExpression, UpdateExpression, UnaryExpression, Literal, Property, SpreadElement, RestElement, ThisExpression, Super, TemplateLiteral, and the TSAsExpression/TSTypeAssertion/TSSatisfiesExpression/ TSNonNullExpression family are aliases for their TypeScript-Go equivalents.

Attribute paths expose type, name, value/raw, operator, variable kind, async, generator, static, readonly, declare, optional, computed, prefix, and the usual structural fields (id, params, body, callee, arguments, object, property, left, right, argument, expression, test, consequent, alternate, init, update, declarations, elements, members, properties, key, source, and statements). Regular expressions use Go’s linear-time RE2 syntax; i, m, and s change matching and u is accepted as an explicit no-op because Go strings are already UTF-8. Numeric segments index node-list paths, so selectors such as [params.0.name='value'] are supported. Boolean properties are absent on node kinds that do not define them, rather than appearing as a synthetic false on every node.

Tree descent and parent relationships use TypeScript-Go’s ForEachChild and Parent surfaces. Sibling and child-position selectors stay within one of the exposed node-list fields above, matching esquery’s array-field semantics. This makes TypeScript-only nodes selectable without constructing or monkeypatching a second ESTree.

Example:

function runWith(target: { value: number }): number { let total = 0; // reports: no-restricted-syntax (error) with (target) { total = value; } return total; }

no-return-assign

Reject assignment expressions used as the operand of return (return x = 1), almost always a typo for ===.

Follows ESLint’s default except-parens option: an assignment wrapped in explicit parentheses (return (count = 1)) is treated as intentional and left alone. Pass the "always" option to flag the parenthesized form as well.

Example:

let count = 0; function f() { // reports: no-return-assign (error) return count = 1; }

no-script-url

Reject javascript: URLs in string literals, they execute their body as code on browser navigation, and security scanners treat them as an eval equivalent.

Example:

// reports: no-script-url (error) const u: string = "javascript:alert(1)";

no-self-assign

Reject x = x and destructuring forms that copy a value to itself, almost always a typo.

Example:

let x = 1; console.log(x); // reports: no-self-assign (error) x = x; console.log(x);

no-self-compare

Reject comparing a value to itself (x === x). Use Number.isNaN(x) to test for NaN.

Example:

function f(a: number) { // reports: no-self-compare (error) return a === a; }

no-sequences

Reject comma expressions (a, b) outside the heads of for statements.

Example:

function f(index: number) { // reports: no-sequences (error) return (index++, index); }

no-setter-return

Reject explicit return from a setter, setters’ return values are ignored.

Example:

class Holder { set value(input: string) { // reports: no-setter-return (error) return "ignored"; } }

no-shadow

Reject a variable declaration that shadows a same-name binding in an enclosing scope.

Example:

let outer: number = 1; function f(): number { // reports: no-shadow (error) let outer: number = 2; return outer; }

no-shadow-restricted-names

Reject redeclaring restricted globals (NaN, Infinity, undefined, etc.).

Example:

// reports: no-shadow-restricted-names (error) function f(undefined: number) { return undefined; } f(1);

no-sparse-arrays

Reject array literals with elision ([, 1, , 3]), which read surprisingly and rarely express intent.

Example:

// reports: no-sparse-arrays (error) const a = [1, , 3];

no-template-curly-in-string

Reject ${expr} inside ordinary single- or double-quoted strings, almost always a missing template-literal backtick.

Example:

// reports: no-template-curly-in-string (error) const s: string = "hello ${name}";

no-this-before-super

Reject this (or super.x) references that precede the first super() call in a derived constructor.

The runtime throws a ReferenceError on the first such access; catching it at lint time avoids a class of bugs that only surface after the constructor is actually called.

Example:

class UsesThisFirst extends Base { constructor() { // reports: no-this-before-super (error) this.value = 1; super(0); } }

no-throw-literal

Reject throwing non-Error operands (throw "boom", throw 1).

Example:

function f() { // reports: no-throw-literal (error) throw "literal"; }

no-undef-init

Reject initializing a variable to the literal undefined (let x = undefined), declaring without an initializer has the same effect.

Example:

// reports: no-undef-init (error) let value = undefined;

no-undefined

Reject use of the global undefined identifier; use the void 0 expression or omit the value.

Example:

// reports: no-undefined (error) const x = undefined;

no-unneeded-ternary

Reject cond ? true : false and similar ternaries that can be simplified to a boolean coercion or the condition itself.

Example:

function f(isReady: boolean) { // reports: no-unneeded-ternary (error) return isReady ? true : false; }

no-unreachable

Reject statements that follow an unconditional return, throw, break, or continue in the same block, control flow has already left the block, so any later statement is dead code.

The conservative baseline scans the immediate statement list of a block (or the top-level source / module body) only.

Hoistable function declarations following the terminator are exempt because they are hoisted above the unreachable point.

Example:

function afterReturn(): number { return 1; // reports: no-unreachable (error) console.log("dead"); }

no-unsafe-finally

Reject return and throw inside a finally block, which override any earlier return/throw from the corresponding try/catch.

Example:

function f() { try { throw new Error("x"); } finally { // reports: no-unsafe-finally (error) return 1; } }

no-unsafe-negation

Reject !key in obj and !a instanceof B where the ! binds tighter than the relational operator and silently coerces the left operand to a boolean.

Wrap in parens (!(key in obj)) when the negation is genuinely intended.

Example:

function hasUnsafeNegation(key: string, object: Record<string, unknown>) { // reports: no-unsafe-negation (error) return !key in object; }

no-unsafe-optional-chaining

Reject member access or call expressions that chain off an optional chain without continuing the chain. (obj?.foo).bar throws a TypeError if obj is null/undefined; the chain must continue with ?. to remain safe.

Example:

const obj: { profile?: { name: string } } | undefined = {}; // reports: no-unsafe-optional-chaining (error) const name = (obj?.profile).name;

no-unused-expressions

Reject expression statements with no observable effect, like a bare x;, a === b;, or a tagged template literal statement.

Directive prologues are determined by AST position, not by recognized text: the leading run of string-literal statements at the top of a script, module, namespace body, or function body is accepted whatever it says ("use strict", "use client", and any future directive), and the same strings anywhere else are rejected. Productive expressions (calls, new, assignments, updates, delete, void, await, yield) are accepted, so the void promise() opt-out idiom keeps working, and JSX statements are accepted by default. TypeScript wrappers (as, angle assertions, non-null !, instantiation expressions) inherit the classification of the expression they wrap.

Options:

  • allowShortCircuit?: boolean

    Allow short-circuit statements such as a && b() whose right-hand side is a productive expression.

  • allowTernary?: boolean

    Allow ternary statements such as a ? b() : c() whose result branches are both productive expressions.

  • allowTaggedTemplates?: boolean

    Allow tagged template literal statements such as tag`value`.

  • enforceForJSX?: boolean

    Report JSX elements and fragments standing alone as statements.

  • ignoreDirectives?: boolean

    Also exempt statements that positionally look like directive-prologue members under the loose ESTree view upstream ESLint uses, in which parentheses are invisible. Real (unparenthesized) directive prologues are always exempt regardless of this flag.

Example:

"use client"; declare function work(): Promise<void>; declare const tag: (strings: TemplateStringsArray) => string; void work(); // reports: no-unused-expressions (error) tag`value`; function misplacedDirective(): void { "use totally custom prologue"; console.log("before"); // reports: no-unused-expressions (error) "use strict"; }

no-unused-labels

Reject labels that no break or continue statement references.

Usually the targeted statement was renamed or removed but the label was left behind.

Example:

// reports: no-unused-labels (error) unused: { }

no-useless-assignment

Reject an assignment whose value is immediately overwritten by the very next statement without an intervening read of the same identifier.

The conservative baseline only fires on two syntactically adjacent x = <expr>; statements where the left-hand sides are the same bare identifier and the second statement’s right-hand side does not reference x itself.

Almost always a leftover from refactoring.

Example:

function deadStore(): number { let x = 0; // reports: no-useless-assignment (error) x = 1; x = 2; return x; }

no-useless-call

Reject unnecessary .call() / .apply() calls (such as f.call(undefined, x)).

Example:

function f() {} // reports: no-useless-call (error) f.call(undefined, 1);

no-useless-catch

Reject catch (e) { throw e } patterns that only rethrow the caught error without adding context or handling.

Example:

function f() { try { return 1; // reports: no-useless-catch (error) } catch (e) { throw e; } }

no-useless-computed-key

Reject computed property keys whose expression is a literal identifier ({ ["foo"]: 1 }).

Example:

// reports: no-useless-computed-key (error) const user = { ["displayName"]: "Ada" };

no-useless-concat

Reject "a" + "b" and similar concatenations where every operand is a literal string.

Example:

// reports: no-useless-concat (error) const s = "a" + "b";

no-useless-constructor

Reject empty constructor bodies (class X { constructor() {} }) that add nothing over the implicit constructor.

Example:

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

no-useless-escape

Reject unnecessary escape sequences in strings and regex literals, such as "\." or /\,/. Autofixable.

Example:

// reports: no-useless-escape (error) const value = "ab\cdef";

no-useless-rename

Reject { x: x } destructuring renames that bind back to the same name. Autofixable, except when a comment sits between the two names: deleting the rename tail would delete the comment, so the collapse is offered as an editor suggestion instead.

Example:

const user = { displayName: "Ada" }; // reports: no-useless-rename (error) const { displayName: displayName } = user;

no-useless-return

Reject a bare return; whose only effect is to end a function body that would have returned anyway.

The conservative baseline only fires on the last statement of a function-like’s immediate body, earlier return; inside a branch or loop may still be load-bearing.

Example:

function trailing(): void { console.log("work"); // reports: no-useless-return (error) return; }

no-var

Reject var declarations.

Use let for mutable bindings and const for immutable ones. Autofixable to let.

Every var declaration list reports, whether it appears as a statement or directly in a for, for...in, or for...of header.

The fix only fires when the rewrite cannot change behavior, mirroring ESLint’s no-var fix conditions: the name binds exactly once in the file, is never read before the declaration or inside its own initializer, every reference stays inside the declaring block scope (a var declared in a block but read after it would stop compiling as let; a loop-header var read after its loop likewise), a loop var — declared in the body or in the header itself — is not captured by a function or arrow created inside the loop (those closures share one var binding but would capture a fresh per-iteration let binding), and the declaration does not sit under a with statement. A for...in/for...of header additionally declines when its declarator carries an Annex-B initializer (for (var i = 0 in o), a SyntaxError under let) or when the head expression reads the loop variable (for (var x of x), a TDZ ReferenceError under let). Anything else reports without a fix.

Example:

// reports: no-var (error) var legacy = 1; // reports: no-var (error) for (var index = 0; index < 1; index += 1) { JSON.stringify(index); }

no-with

Reject with (...) statements.

with is forbidden in strict mode (and therefore in modules), defeats lexical scoping, and blocks engine optimization.

Example:

function f(scope: Record<string, unknown>) { // reports: no-with (error) with (scope) { console.log("hi"); } }

object-shorthand

Reject { foo: foo } and similar object-literal shorthand candidates in favor of { foo }. Autofixable, except when a comment sits between the key and the value: deleting the : value tail would delete the comment, so the collapse is offered as an editor suggestion instead.

Example:

const x = 1; // reports: object-shorthand (error) const o = { x: x };

operator-assignment

Prefer compound assignment (x += y) over the long form (x = x + y) where the two are equivalent.

Example:

let x = 1; console.log(x); // reports: operator-assignment (error) x = x + 1; console.log(x);

prefer-arrow-callback

Reject function() { ... } expressions passed as callback arguments, prefer the arrow form.

Example:

const list: readonly number[] = [1, 2, 3]; list.map( // reports: prefer-arrow-callback (error) function (n: number) { return n * 2; }, );

prefer-const

Require const for lexical bindings that are never reassigned after their initial value is established. Same-spelled bindings in sibling, nested, and shadowing scopes are analyzed independently. A declaration followed by one assignment in the same scope is reported without an automatic rewrite.

The default options match ESLint:

{ "destructuring": "any", "ignoreReadBeforeAssign": false }

destructuring: "any" reports each const-eligible leaf of a destructuring pattern. Set it to "all" to report the pattern only when every leaf is const-eligible. Set ignoreReadBeforeAssign to true to keep declaration-only bindings that are read before their first assignment out of the findings.

The fixer changes the shared let keyword only for a single initialized declaration when every binding affected by that keyword is const-eligible. Findings that would require moving an assignment, splitting a declaration, or preserving comments across statements are diagnostic-only.

Example:

// reports: prefer-const (error) let stable = 1; let changing = 1; changing = 2; let assignedLater: number; // reports: prefer-const (error, diagnostic-only) assignedLater = 1; const input = { first: 1, second: 2 }; let { first, second } = input; // reports `second` first += 1;

prefer-destructuring

Reject single-property and single-index variable declarations (const a = obj.a, const x = arr[0]) that destructuring would replace verbatim.

Example:

const obj = { a: 1, b: 2 }; // reports: prefer-destructuring (error) const a = obj.a;

prefer-exponentiation-operator

Prefer the ** operator over Math.pow(base, exp).

Example:

// reports: prefer-exponentiation-operator (error) const a = Math.pow(2, 3);

prefer-for-of

Prefer for..of over a traditional for (let i = 0; i < arr.length; i++) loop when the index is never used inside the body.

Example:

const arr: number[] = [1, 2, 3]; // reports: prefer-for-of (error) for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }

prefer-object-has-own

Prefer Object.hasOwn(obj, key) over Object.prototype.hasOwnProperty.call(obj, key). The new helper is shorter, less error-prone, and matches the form linters elsewhere recommend.

Example:

const target = { x: 1 }; // reports: prefer-object-has-own (error) const a = Object.prototype.hasOwnProperty.call(target, "x");

prefer-object-spread

Prefer object-spread { ...a, ...b } over Object.assign({}, a, b).

Only fires when the first argument is an empty object literal, mutating Object.assign(target, ...) calls are left alone because the spread form does not preserve their observable side effects.

Example:

const source = { x: 1 }; // reports: prefer-object-spread (error) const merged = Object.assign({}, source);

prefer-named-capture-group

Reject regex literals with unnamed capturing groups (...), prefer named groups (?<name>...).

Example:

// reports: prefer-named-capture-group (error) const yearOnly = /(\d{4})/;

prefer-numeric-literals

Prefer ES2015+ numeric literal forms (0b..., 0o..., 0x...) over parseInt(string, 2 | 8 | 16). The literal form is shorter, type- safe at lint time, and not subject to runtime radix mismatches.

Example:

// reports: prefer-numeric-literals (error) const bin = parseInt("11", 2);

prefer-rest-params

Reject reading from arguments in a non-arrow function body, prefer the ES2015 rest-parameter form (...args), which declares the variadic contract on the signature and yields a real array.

Example:

function sumLegacy() { // reports: prefer-rest-params (error) return Array.prototype.slice .call(arguments) .reduce((a: number, b: number) => a + b, 0); }

prefer-spread

Prefer spread arguments f(...args) over f.apply(null, args).

Only flags apply calls whose this argument is provably the same receiver (or null / undefined); calls that genuinely rebind this are left alone.

Example:

function f(a: number, b: number) { return a + b; } const args: [number, number] = [1, 2]; // reports: prefer-spread (error) f.apply(null, args);

prefer-template

Prefer template literals over string concatenation when any operand is non-literal.

The autofix rewrites the whole + chain as one template literal. A comment in an operator seam has nowhere to go in the rebuilt literal, so that case is reported without an autofix and the rendered literal is offered as an editor suggestion (applying it discards the seam comment).

Example:

const name = "world"; // reports: prefer-template (error) const s = "hi " + name + "!";

radix

Require an explicit radix argument for parseInt(str, radix).

Without it, "0123" parses as decimal or octal depending on the engine.

Example:

// reports: radix (error) const n = parseInt("42");

require-yield

Require generator functions to contain at least one yield. A yield-less generator is almost always a typo.

Example:

// reports: require-yield (error) function* gen() { return 1; }

sort-imports

Reject import specifiers within a single import declaration that aren’t alphabetically sorted.

Example:

// reports: sort-imports (error) import { a, b } from "first";

sort-keys

Reject object-literal property keys that aren’t alphabetically sorted.

Example:

const unsorted = { b: 1, // reports: sort-keys (error) a: 2, };

use-isnan

Require Number.isNaN / isNaN for NaN checks; restrict typeof comparisons to the documented strings.

Example:

function f(x: number) { // reports: use-isnan (error) return x === NaN; }

valid-typeof

Restrict the right-hand operand of typeof to the documented strings ("number", "object", …) so typeof x === "undefiend" typos are caught.

Only equality comparisons (==, ===, !=, !==) are checked. A relational comparison such as typeof x < "m" orders two strings instead of naming a type, so it is left alone.

Example:

function f(value: unknown) { // reports: valid-typeof (error) return typeof value === "stirng"; }

vars-on-top

Require var declarations to be hoisted to the top of their scope by hand, mirroring how the engine treats them.

Has no effect when no-var forbids var altogether.

Example:

function f() { console.log("hi"); // reports: vars-on-top (error) var a = 1; } f();

yoda

Reject Yoda-style comparisons (if (42 === x)); use if (x === 42) so the variable comes first.

Example:

function f(x: number) { // reports: yoda (error) return 1 === x; }
Last updated on