Skip to Content

Functional

Functional-programming policy rules from eslint-plugin-functional. They push code toward immutability, side-effect-free expressions, and expression-style control flow.

Most rules are useful in pieces. Projects rarely enable the whole family at "error". Enabling the whole set together expresses a strict functional-core / imperative-shell discipline.

Source: eslint-plugin-functional (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.

Configuration example

// lint.config.ts import type { ITtscLintConfig } from "@ttsc/lint"; export default { rules: { "functional/no-let": "error", "functional/no-loop-statements": "error", "functional/no-conditional-statements": "error", "functional/no-throw-statements": "error", "functional/no-try-statements": "error", "functional/no-classes": "error", "functional/immutable-data": "error", "functional/prefer-readonly-type": "error", "functional/type-declaration-immutability": [ "error", { rules: [{ identifiers: ".*" }], }, ], }, } satisfies ITtscLintConfig;

Rules

functional/functional-parameters

Enforce functional parameter style: reject arguments, reject rest parameters, and optionally enforce a per-function parameter-count policy.

Options:

  • allowRestParameter?: boolean

    Allow rest parameters such as (...args: readonly string[]).

  • allowArgumentsKeyword?: boolean

    Allow the legacy arguments object.

  • enforceParameterCount?: boolean | "atLeastOne" | "exactlyOne"

    Require functions to declare parameters. true maps to the conservative at-least-one policy.

Example:

// reports: functional/functional-parameters (error) function variadic(...args: number[]): number { return args.length; }

functional/immutable-data

Reject property assignment (obj.x = ...), element assignment (arr[0] = ...), and Map/Set mutation methods. Configurable to allow mutation inside constructors or initialization expressions.

Options:

  • ignoreMapsAndSets?: boolean

    Skip mutating Map and Set methods while still checking arrays and property assignment.

Example:

const obj = { value: 1 }; // reports: functional/immutable-data (error) obj.value = 2;

functional/no-class-inheritance

Reject abstract classes and extends clauses on class declarations; prefer composition and structural typing.

Example:

class Base {} // reports: functional/no-class-inheritance (error) class Derived extends Base {}

functional/no-classes

Reject class declarations and class expressions altogether.

Example:

// reports: functional/no-classes (error) class Container { value = 1; }

functional/no-conditional-statements

Reject if and switch statements. The conditional-expression forms (ternary, &&, ||) remain allowed.

Options:

  • allowReturningBranches?: boolean | "ifExhaustive"

    Reserved for upstream-compatible configs; the current native rule rejects all if / switch statements regardless of value.

Example:

const flag = true; // reports: functional/no-conditional-statements (error) if (flag) { }

functional/no-expression-statements

Reject expression statements that exist purely for their side effects (mutate(x);); pure code is built up from expressions with assigned or returned results.

Example:

// reports: functional/no-expression-statements (error) console.log("saved");

functional/no-let

Reject let declarations so every binding is const. The shared ignore-pattern options can carve out specific identifier shapes (test locals, loop counters) when a full ban is too aggressive.

Options:

  • allowInForLoopInit?: boolean

    Permit let in a for statement initializer.

  • allowInFunctions?: boolean

    Permit let inside functions while still rejecting module-level let.

Example:

// reports: functional/no-let (error) let count = 0;

functional/no-loop-statements

Reject for, while, and do/while loop statements; use recursive helpers or array methods (map, reduce) instead.

Example:

const items = [1, 2, 3]; // reports: functional/no-loop-statements (error) for (const item of items) { }

functional/no-mixed-types

Reject interfaces and type literals that mix property, method, call, and index member kinds; mixed shapes make composition harder.

Options:

  • checkInterfaces?: boolean

    Check interface member kinds.

  • checkTypeLiterals?: boolean

    Check type-literal member kinds.

Example:

type Mixed = { value: number; // reports: functional/no-mixed-types (error) compute(): number; };

functional/no-promise-reject

Reject any call to Promise.reject(...); resolve with an Option / Result shape so failures stay in the value channel.

Example:

// reports: functional/no-promise-reject (error) const rejection = Promise.reject(new Error("nope"));

functional/no-return-void

Reject void returns and functions whose declared return type is void; functions should always return a value.

Options:

  • allowNull?: boolean

    Permit a function that returns null to satisfy the rule.

  • allowUndefined?: boolean

    Permit a function that returns undefined to satisfy the rule.

  • ignoreInferredTypes?: boolean

    Skip functions whose return type is inferred to be void rather than declared explicitly.

Example:

// reports: functional/no-return-void (error) function log(): void { // reports: functional/no-return-void (error) return; }

functional/no-this-expressions

Reject any this expression. Combined with functional/no-classes this leaves no place where this is legal; on its own it still forbids this in standalone functions, modules, and arrow pipelines.

Example:

const value: unknown = "current"; function read(this: { value: unknown }) { // reports: functional/no-this-expressions (error) return this.value; }

functional/no-throw-statements

Reject throw statements; functional code surfaces failures through return values (Either, Result) instead.

Options:

  • allowToRejectPromises?: boolean

    Reserved for upstream-compatible configs; the current native rule rejects every throw statement regardless of value.

Example:

function boom(): never { // reports: functional/no-throw-statements (error) throw new Error("boom"); }

functional/no-try-statements

Reject try/catch and try/finally statements as a corollary of functional/no-throw-statements.

Options:

  • allowCatch?: boolean

    Allow try/catch while still checking finally when present.

  • allowFinally?: boolean

    Allow try/finally while still checking catch when present.

Example:

function run(): void {} function recover(error: unknown): void { console.error(error); } // reports: functional/no-try-statements (error) try { run(); } catch (error) { recover(error); }

functional/prefer-immutable-types

Require declared variable, parameter, and property types to be readonly or otherwise structurally immutable.

Options:

  • enforcement?: | "ReadonlyShallow" | "ReadonlyDeep" | "Immutable" | "None" | false

    Minimum accepted immutability. The native subset treats any configured value as readonly-required.

Example:

// reports: functional/prefer-immutable-types (error) const values: string[] = [];

functional/prefer-property-signatures

Prefer function-property signatures (fn: () => T) over method shorthand (fn(): T) in interfaces and type literals.

Only the property form accepts readonly, so method shorthand silently makes the slot mutable; overloads still need the method form.

Example:

interface Api { // reports: functional/prefer-property-signatures (error) run(): void; }

functional/prefer-readonly-type

Prefer readonly array, tuple, collection, and property types over their mutable counterparts.

Options:

  • allowLocalMutation?: boolean

    Permit mutation of locals while still policing exported types.

  • allowMutableReturnType?: boolean

    Permit a mutable return type even when parameters must be readonly.

  • checkImplicit?: boolean

    Also check property positions that have no explicit type annotation.

  • ignoreCollections?: boolean

    Skip array / tuple / Map / Set types.

  • ignoreClass?: boolean | "fieldsOnly"

    Skip class fields. "fieldsOnly" keeps the rule active for non-field class members.

  • ignoreInterface?: boolean

    Skip interface members entirely.

Example:

// reports: functional/prefer-readonly-type (error) type Values = string[];

functional/prefer-tacit

Reject trivial wrappers such as x => f(x) in favor of the tacit form f; reduces noise in functional pipelines.

Options:

  • checkMemberExpressions?: boolean

    Check member expressions such as x => service.map(x).

Example:

function transform(value: number): number { return value * 2; } // reports: functional/prefer-tacit (error) const map = (value: number) => transform(value);

functional/readonly-type

Enforce one consistent spelling for readonly types, readonly T[] vs ReadonlyArray<T>, across the project.

Options:

  • prefer?: "keyword" | "generic"

    Preferred readonly spelling. Default: "keyword".

Example:

// reports: functional/readonly-type (error) type Values = ReadonlyArray<string>;

functional/type-declaration-immutability

Enforce readonly/immutable type declarations by declaration name policy, for projects that want only types matching certain naming conventions to be locked down.

Options:

  • rules?: readonly ITtscLintFunctionalTypeDeclarationImmutabilityRule[]

    Declaration-name policies. Empty means all type declarations.

  • ignoreInterfaces?: boolean

    Skip interface declarations and only check type aliases.

Example:

// reports: functional/type-declaration-immutability (error) interface State { values: string[]; }
Last updated on