Skip to content

Finding Reference

This page is generated by docs/generate.py from src/noze/glossary/docs.rs.

API Clarity and Constants

Boolean Blindness (boolean_blindness)

What it is

Bare booleans whose meaning is invisible at the call site (f(true, false)) — use a closed type (enum or literal union) or named options so calls read clearly.

Why it's bad

Call sites stop reading like code and start reading like truth tables.

Example

Problem

def publish_invoice(
    invoice_id: str,
    email_customer: bool,
    archive_pdf: bool,
    notify_owner: bool,
) -> None:
    invoice = repo.load(invoice_id)
    if email_customer:
        mailer.send(invoice)
    else:
        review_queue.add(invoice)
    if archive_pdf:
        archive.store(render_pdf(invoice))
    if notify_owner:
        notifier.ping(invoice.owner)

Proposed fix

Promote each boolean decision into a named strategy so callers choose behavior explicitly.

from typing import Protocol


class InvoiceDelivery(Protocol):
    def deliver(self, invoice: Invoice) -> None: ...


class InvoiceFollowUp(Protocol):
    def run(self, invoice: Invoice) -> None: ...


class EmailDelivery:
    def deliver(self, invoice: Invoice) -> None:
        mailer.send(invoice)


class ReviewQueueDelivery:
    def deliver(self, invoice: Invoice) -> None:
        review_queue.add(invoice)


class PdfArchival:
    def run(self, invoice: Invoice) -> None:
        archive.store(render_pdf(invoice))


class OwnerNotification:
    def run(self, invoice: Invoice) -> None:
        notifier.ping(invoice.owner)


def publish_invoice(
    invoice_id: str,
    delivery: InvoiceDelivery,
    follow_ups: list[InvoiceFollowUp],
) -> None:
    invoice = repo.load(invoice_id)
    delivery.deliver(invoice)
    for follow_up in follow_ups:
        follow_up.run(invoice)

Problem

function publishInvoice(
  invoiceId: string,
  emailCustomer: boolean,
  archivePdf: boolean,
  notifyOwner: boolean,
): void {
  const invoice = repo.load(invoiceId);
  if (emailCustomer) {
    mailer.send(invoice);
  } else {
    reviewQueue.add(invoice);
  }
  if (archivePdf) {
    archive.store(renderPdf(invoice));
  }
  if (notifyOwner) {
    notifier.ping(invoice.owner);
  }
}

Proposed fix

Promote each boolean decision into a named strategy so callers choose behavior explicitly.

interface InvoiceDelivery {
  deliver(invoice: Invoice): void;
}

interface InvoiceFollowUp {
  run(invoice: Invoice): void;
}

class EmailDelivery implements InvoiceDelivery {
  deliver(invoice: Invoice): void {
    mailer.send(invoice);
  }
}

class ReviewQueueDelivery implements InvoiceDelivery {
  deliver(invoice: Invoice): void {
    reviewQueue.add(invoice);
  }
}

class PdfArchival implements InvoiceFollowUp {
  run(invoice: Invoice): void {
    archive.store(renderPdf(invoice));
  }
}

class OwnerNotification implements InvoiceFollowUp {
  run(invoice: Invoice): void {
    notifier.ping(invoice.owner);
  }
}

function publishInvoice(
  invoiceId: string,
  delivery: InvoiceDelivery,
  followUps: InvoiceFollowUp[],
): void {
  const invoice = repo.load(invoiceId);
  delivery.deliver(invoice);
  for (const followUp of followUps) {
    followUp.run(invoice);
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.boolean_blindness]
enabled = true
action = "warning"
max_bool_params = 2 # allowed boolean parameters before flagging
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Literal Membership (literal_membership)

What it is

Branching on membership in a literal string list, or on three or more ===/== string comparisons of one value joined by ||/or — stringly-typed categories; use a closed type (enum or literal union). Punctuation sets and markup tag names are outside vocabularies and stay quiet.

Why it's bad

Hard-coded string sets become a hidden enum that tools cannot help with.

Example

Problem

def is_allowed(status: str) -> bool:
    return status in ["draft", "paid", "void"]

Proposed fix

Use an Enum or named constant set with a typed boundary.

from enum import Enum


class InvoiceStatus(Enum):
    DRAFT = "draft"
    PAID = "paid"
    VOID = "void"


def is_allowed(status: InvoiceStatus) -> bool:
    return status in {
        InvoiceStatus.DRAFT,
        InvoiceStatus.PAID,
        InvoiceStatus.VOID,
    }

Problem

function isAllowed(status: string): boolean {
  return ["draft", "paid", "void"].includes(status);
}

Proposed fix

Use a string-literal union type or a named as const set shared with its type.

enum InvoiceStatus {
  Draft = "draft",
  Paid = "paid",
  Void = "void",
}

function isAllowed(status: InvoiceStatus): boolean {
  return status === InvoiceStatus.Draft ||
    status === InvoiceStatus.Paid ||
    status === InvoiceStatus.Void;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.literal_membership]
enabled = true
action = "warning"
skip_external_vocab = true # ignore extension, MIME, path, and error-code lists
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Magic String Default (magic_string_default)

What it is

A fallback empty, one-character, or placeholder string (?? "", or "n/a") is stored, compared, or returned in place of a missing value — the contract is hiding in a sentinel; prefer a nullable/optional string or a dedicated sum type.

Why it's bad

A sentinel string hides the real optionality of the value.

Example

Flags empty, one-character, or sentinel-word fallbacks that are stored, sent, or compared.

Problem

def save_owner(owner: Owner) -> None:
    store.save(label=owner.label or "")

Proposed fix

Reject a missing required string explicitly instead of hiding it behind a sentinel fallback.

def display_name(name: str | None) -> str:
    if name is None:
        raise ValueError("display name is required")
    return name

Also flags empty or one-character fallbacks that are returned or kept in a variable; skips rendered text and string operations, including coercions such as Number(..) and lookups such as map.get(..).

Problem

def display_name(name: str | None) -> str:
    return name or ""

Proposed fix

Reject a missing required string explicitly instead of hiding it behind a sentinel fallback.

def display_name(name: str | None) -> str:
    if name is None:
        raise ValueError("display name is required")
    return name

Flags every empty, one-character, or sentinel-word fallback wherever it flows.

Problem

def greeting(name: str | None) -> str:
    return f"Hello {name or ''}"

Proposed fix

Reject a missing required string explicitly instead of hiding it behind a sentinel fallback.

def display_name(name: str | None) -> str:
    if name is None:
        raise ValueError("display name is required")
    return name

Flags empty, one-character, or sentinel-word fallbacks that are stored, sent, or compared.

Problem

async function saveOwner(owner: Owner): Promise<void> {
  await store.save({ label: owner.label ?? "n/a" });
}

Proposed fix

Throw on a missing required string, or model optionality explicitly when absence is valid.

function displayName(name?: string): string {
  if (name === undefined) {
    throw new Error("display name is required");
  }
  return name;
}

Also flags empty or one-character fallbacks that are returned or kept in a variable; skips rendered text and string operations, including coercions such as Number(..) and lookups such as map.get(..).

Problem

function displayName(name?: string): string {
  return name || "?";
}

Proposed fix

Throw on a missing required string, or model optionality explicitly when absence is valid.

function displayName(name?: string): string {
  if (name === undefined) {
    throw new Error("display name is required");
  }
  return name;
}

Flags every empty, one-character, or sentinel-word fallback wherever it flows.

Problem

function greeting(name?: string): string {
  return `Hello ${name ?? ""}`;
}

Proposed fix

Throw on a missing required string, or model optionality explicitly when absence is valid.

function displayName(name?: string): string {
  if (name === undefined) {
    throw new Error("display name is required");
  }
  return name;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.magic_string_default]
enabled = true
action = "warning"
strictness = "low"

Flags empty, one-character, or sentinel-word fallbacks that are stored, sent, or compared.

[smells.<lang>.rules.magic_string_default]
enabled = true
action = "warning"
strictness = "medium"

Also flags empty or one-character fallbacks that are returned or kept in a variable; skips rendered text and string operations, including coercions such as Number(..) and lookups such as map.get(..).

[smells.<lang>.rules.magic_string_default]
enabled = true
action = "warning"
strictness = "high"

Flags every empty, one-character, or sentinel-word fallback wherever it flows.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Magic Numbers (magic_numbers)

What it is

Unexplained numeric literals in logic — name them as constants so their intent is clear.

Why it's bad

Numbers without names are hard to audit and easy to copy blindly.

Example

Problem

def is_retryable(attempts: int) -> bool:
    return attempts < 7

Proposed fix

Extract a named constant near the policy it represents.

MAX_RETRIES = 7


def is_retryable(attempts: int) -> bool:
    return attempts < MAX_RETRIES

Problem

function isRetryable(attempts: number): boolean {
  return attempts < 7;
}

Proposed fix

Use a named const or configuration value for the policy number.

const MAX_RETRIES = 7;

function isRetryable(attempts: number): boolean {
  return attempts < MAX_RETRIES;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.magic_numbers]
enabled = true
action = "warning"
ignore = [] # numeric literals never reported
ignore_array_indexes = true # skip literal array indexes such as x[3]
ignore_default_values = true # skip literals in parameter default values
allow_http_status = true # skip 100-599 compared or assigned to a status field
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • Ruff: PLR2004
  • ESLint: no-magic-numbers

Complexity and Control Flow

Deep Nesting (deep_nesting)

What it is

Control flow nests many levels deep, hard to follow — flatten with early returns or extracted helpers.

Why it's bad

The control flow becomes hard to scan and easy to misread in review.

Example

Problem

def approve(order: Order) -> bool:
    if order.is_paid:
        if not order.is_flagged:
            if order.customer.is_active:
                if order.customer.is_verified:
                    if order.total < order.customer.limit:
                        return True
    return False

Proposed fix

Use guard clauses, continue early, or extract a helper for the nested branch.

def approve(order: Order) -> bool:
    if not order.is_paid or order.is_flagged:
        return False
    if not order.customer.is_active:
        return False
    if not order.customer.is_verified:
        return False
    return order.total < order.customer.limit

Problem

function approve(order: Order): boolean {
  if (order.isPaid) {
    if (!order.isFlagged) {
      if (order.customer.isActive) {
        if (order.customer.isVerified) {
          if (order.total < order.customer.limit) {
            return true;
          }
        }
      }
    }
  }
  return false;
}

Proposed fix

Flatten with early returns/continues or pull nested checks into named helpers.

function approve(order: Order): boolean {
  if (!order.isPaid || order.isFlagged) {
    return false;
  }
  if (!order.customer.isActive) {
    return false;
  }
  if (!order.customer.isVerified) {
    return false;
  }
  return order.total < order.customer.limit;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.deep_nesting]
enabled = true
action = "warning"
max_nesting = 4 # allowed nested block depth
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSNo
RustYes

External linter coverage

  • Ruff: PLR1702
  • ESLint: max-depth

High Cognitive Complexity (high_cognitive_complexity)

What it is

Hard for a human to follow — nested branches and loops weighted by depth; simplify or break it up.

Why it's bad

The reader has to simulate too many branches and nesting levels at once.

Example

Problem

def score(order: Order) -> int:
    total = 0
    if order.paid:
        if order.customer.active:
            if not order.customer.suspended:
                if order.customer.verified:
                    if order.total >= 500:
                        total += 25
                    elif order.total >= 100:
                        total += 10
                    else:
                        total += 1
    return total

Proposed fix

Name the decision steps, flatten branches, and extract cohesive helpers.

NO_SCORE = 0
ENTERPRISE_ORDER_THRESHOLD = 500
LARGE_ORDER_THRESHOLD = 100
ENTERPRISE_ORDER_SCORE = 25
LARGE_ORDER_SCORE = 10
STANDARD_ORDER_SCORE = 1


def score(order: Order) -> int:
    if not can_score(order):
        return NO_SCORE
    return score_total(order.total)


def can_score(order: Order) -> bool:
    if not order.paid:
        return False
    if not order.customer.active:
        return False
    if order.customer.suspended:
        return False
    return order.customer.verified


def score_total(total: int) -> int:
    if total >= ENTERPRISE_ORDER_THRESHOLD:
        return ENTERPRISE_ORDER_SCORE
    if total >= LARGE_ORDER_THRESHOLD:
        return LARGE_ORDER_SCORE
    return STANDARD_ORDER_SCORE

Problem

function score(order: Order): number {
  let total = 0;
  if (order.paid) {
    if (order.customer.active) {
      if (!order.customer.suspended) {
        if (order.customer.verified) {
          if (order.total >= 500) {
            total += 25;
          } else if (order.total >= 100) {
            total += 10;
          } else {
            total += 1;
          }
        }
      }
    }
  }
  return total;
}

Proposed fix

Prefer guard clauses and small predicate functions over nested branches.

const NO_SCORE = 0;
const ENTERPRISE_ORDER_THRESHOLD = 500;
const LARGE_ORDER_THRESHOLD = 100;
const ENTERPRISE_ORDER_SCORE = 25;
const LARGE_ORDER_SCORE = 10;
const STANDARD_ORDER_SCORE = 1;

function score(order: Order): number {
  if (!canScore(order)) {
    return NO_SCORE;
  }
  return scoreTotal(order.total);
}

function canScore(order: Order): boolean {
  if (!order.paid) {
    return false;
  }
  if (!order.customer.active) {
    return false;
  }
  if (order.customer.suspended) {
    return false;
  }
  return order.customer.verified;
}

function scoreTotal(total: number): number {
  if (total >= ENTERPRISE_ORDER_THRESHOLD) {
    return ENTERPRISE_ORDER_SCORE;
  }
  if (total >= LARGE_ORDER_THRESHOLD) {
    return LARGE_ORDER_SCORE;
  }
  return STANDARD_ORDER_SCORE;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.high_cognitive_complexity]
enabled = true
action = "warning"
max_cognitive = 15 # allowed cognitive complexity score
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSNo
RustYes

External linter coverage

  • ESLint: sonarjs/cognitive-complexity

High Cyclomatic Complexity (high_complexity)

What it is

Many independent paths through the function, so it's hard to test fully — decompose it.

Why it's bad

The function accumulates too many distinct paths to reason about confidently.

Example

Problem

def choose_refund(order: Order) -> RefundDecision:
    if order.cancelled:
        return deny_refund(order, "cancelled")
    if order.fraud_review:
        return manual_review(order)
    if order.days_since_purchase > 30:
        return deny_refund(order, "expired")
    if order.damaged:
        return full_refund(order)
    if order.missing_items:
        return partial_refund(order)
    if order.vip_customer:
        return goodwill_credit(order)
    return deny_refund(order, "not_eligible")

Proposed fix

Use a dispatch table or split the branches into named operations.

from collections.abc import Callable


RefundHandler = Callable[[Order], RefundDecision]


REFUND_POLICY: dict[str, RefundHandler] = {
    "cancelled": deny_cancelled,
    "fraud_review": manual_review,
    "expired": deny_expired,
    "damaged": full_refund,
    "missing_items": partial_refund,
    "vip_customer": goodwill_credit,
}


def choose_refund(order: Order) -> RefundDecision:
    handler = REFUND_POLICY.get(order.refund_reason(), deny_not_eligible)
    return handler(order)

Problem

function chooseRefund(order: Order): RefundDecision {
  if (order.cancelled) {
    return denyRefund(order, "cancelled");
  }
  if (order.fraudReview) {
    return manualReview(order);
  }
  if (order.daysSincePurchase > 30) {
    return denyRefund(order, "expired");
  }
  if (order.damaged) {
    return fullRefund(order);
  }
  if (order.missingItems) {
    return partialRefund(order);
  }
  if (order.vipCustomer) {
    return goodwillCredit(order);
  }
  return denyRefund(order, "not_eligible");
}

Proposed fix

Use a map, strategy object, or smaller functions for independent paths.

type RefundHandler = (order: Order) => RefundDecision;

const refundPolicy: Record<string, RefundHandler> = {
  cancelled: denyCancelled,
  fraud_review: manualReview,
  expired: denyExpired,
  damaged: fullRefund,
  missing_items: partialRefund,
  vip_customer: goodwillCredit,
};

function chooseRefund(order: Order): RefundDecision {
  const handler = refundPolicy[order.refundReason()] ?? denyNotEligible;
  return handler(order);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.high_complexity]
enabled = true
action = "warning"
max_cyclomatic = 10 # allowed independent control-flow paths
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • Ruff: PLR0912
  • ESLint: complexity

Too Many Returns (too_many_returns)

What it is

Many exit points make the function's flow hard to follow — consolidate, or it's doing too much.

Why it's bad

Many exit points make the function harder to follow and test.

Example

Problem

def status_for(order: Order) -> str:
    if order.cancelled:
        return "cancelled"
    elif order.refunded:
        return "refunded"
    elif order.failed:
        return "failed"
    elif order.paid:
        return "paid"
    return "open"

Proposed fix

Group related guards or extract decision helpers.

def status_for(order: Order) -> str:
    rules = (
        (order.cancelled, "cancelled"),
        (order.refunded, "refunded"),
        (order.failed, "failed"),
        (order.paid, "paid"),
    )
    for applies, status in rules:
        if applies:
            return status
    return "open"

Problem

function statusFor(order: Order): string {
  if (order.cancelled) {
    return "cancelled";
  } else if (order.refunded) {
    return "refunded";
  } else if (order.failed) {
    return "failed";
  } else if (order.paid) {
    return "paid";
  }
  return "open";
}

Proposed fix

Keep meaningful guard clauses, then extract noisy branches into predicates.

const ORDER_STATUS_RULES: Array<[keyof Order, string]> = [
  ["cancelled", "cancelled"],
  ["refunded", "refunded"],
  ["failed", "failed"],
  ["paid", "paid"],
];

function statusFor(order: Order): string {
  for (const [field, status] of ORDER_STATUS_RULES) {
    if (order[field]) {
      return status;
    }
  }
  return "open";
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.too_many_returns]
enabled = true
action = "warning"
max_returns = 4 # allowed return statements before flagging
count_guard_clauses = false # also count top-level guard-clause returns
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • Ruff: PLR0911

Nested Ternary (nested_ternary)

What it is

A conditional expression is a branch or condition of another conditional expression, forcing readers to match several branches mentally — extract the result into a named function or flatten it into if statements with early returns.

Why it's bad

Nested conditional expressions force readers to mentally match several conditions with their results, making agent-generated logic difficult to review and change.

Example

Flags direct chains, where one conditional is a branch or condition of another.

Problem

def delivery_status(order: Order) -> DeliveryStatus:
    return (
        DeliveryStatus.READY
        if order.is_paid
        else DeliveryStatus.RETRY
        if order.payment_retry_allowed
        else DeliveryStatus.BLOCKED
    )

Proposed fix

Extract the decision into a named helper, or replace the expression with ordered if statements and early returns.

def delivery_status(order) -> DeliveryStatus:
    if order.is_paid:
        return DeliveryStatus.READY
    if order.payment_retry_allowed:
        return DeliveryStatus.RETRY
    return DeliveryStatus.BLOCKED

Also flags conditionals nested inside a call argument or template of another; JSX elements and object literals end the nesting.

Problem

def delivery_label(order: Order) -> str:
    return (
        format_label("gift" if order.is_gift else "standard")
        if order.is_paid
        else "pending"
    )

Proposed fix

Extract the decision into a named helper, or replace the expression with ordered if statements and early returns.

def delivery_status(order) -> DeliveryStatus:
    if order.is_paid:
        return DeliveryStatus.READY
    if order.payment_retry_allowed:
        return DeliveryStatus.RETRY
    return DeliveryStatus.BLOCKED

Flags direct chains, where one conditional is a branch or condition of another.

Problem

export function deliveryStatus(order: Order): DeliveryStatus {
  return order.isPaid
    ? DeliveryStatus.Ready
    : order.paymentRetryAllowed
      ? DeliveryStatus.Retry
      : DeliveryStatus.Blocked;
}

Proposed fix

Move result selection into a named function and use explicit if branches with early returns.

function deliveryStatus(order: Order): DeliveryStatus {
  if (order.isPaid) {
    return DeliveryStatus.Ready;
  }
  if (order.paymentRetryAllowed) {
    return DeliveryStatus.Retry;
  }
  return DeliveryStatus.Blocked;
}

Also flags conditionals nested inside a call argument or template of another; JSX elements and object literals end the nesting.

Problem

export function deliveryLabel(order: Order): string {
  return order.isPaid ? formatLabel(order.isGift ? "gift" : "standard") : "pending";
}

Proposed fix

Move result selection into a named function and use explicit if branches with early returns.

function deliveryStatus(order: Order): DeliveryStatus {
  if (order.isPaid) {
    return DeliveryStatus.Ready;
  }
  if (order.paymentRetryAllowed) {
    return DeliveryStatus.Retry;
  }
  return DeliveryStatus.Blocked;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.nested_ternary]
enabled = true
action = "warning"
strictness = "medium"

Flags direct chains, where one conditional is a branch or condition of another.

[smells.<lang>.rules.nested_ternary]
enabled = true
action = "warning"
strictness = "high"

Also flags conditionals nested inside a call argument or template of another; JSX elements and object literals end the nesting.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

External linter coverage

  • ESLint: no-nested-ternary

Unnecessary Nested If (unnecessary_nested_if)

What it is

An if whose only body is another if, with no else path — combine the conditions with and/&& to flatten the control flow.

Why it's bad

The code says 'if' twice when the condition really belongs on one line.

Example

Problem

def can_ship(order: Order) -> bool:
    if order.paid:
        if order.address.is_valid:
            return True
    return False

Proposed fix

Combine the conditions or use a guard clause.

def can_ship(order: Order) -> bool:
    if not order.paid:
        return False
    return order.address.is_valid

Problem

function canShip(order: Order): boolean {
  if (order.paid) {
    if (order.address.isValid) {
      return true;
    }
  }
  return false;
}

Proposed fix

Collapse nested conditions with && or extract a predicate.

function canShip(order: Order): boolean {
  if (!order.paid) {
    return false;
  }
  return order.address.isValid;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.unnecessary_nested_if]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

External linter coverage

  • ESLint: sonarjs/no-collapsible-if

Redundant Validation (redundant_validation)

What it is

The same condition is checked repeatedly in one function — establish the invariant once and simplify the later path.

Why it's bad

Repeated guards obscure which invariants have already been established.

Example

Problem

def publish(message):
    if message is None:
        return
    prepare(message)
    if message is None:
        return
    send(message)

Proposed fix

Validate at the boundary or first use, then rely on the established invariant.

def publish(message):
    if message is None:
        return
    prepare(message)
    send(message)

Problem

function publish(message: Message | undefined) {
  if (message === undefined) return;
  prepare(message);
  if (message === undefined) return;
  send(message);
}

Proposed fix

Validate at the boundary or first use, then rely on the established invariant.

function publish(message: Message | undefined) {
  if (message === undefined) return;
  prepare(message);
  send(message);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.redundant_validation]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

Size and Responsibility

God Module (god_module)

What it is

A sizeable module with its own functions or classes that many modules depend on and that itself depends on many internal modules at runtime (high centrality) — a coupling and change-risk hotspot; split its responsibilities. A composition root of imports and one data object is not one.

Why it's bad

A single module becomes a hot spot with too many reasons to change.

Example

The example is kept short, so it fires with these settings:

# The example stays small; at defaults a god module needs a fan of 25, 150 lines
# and 12 functions or classes, and with four callers shotgun_surgery_hazard wins.
[smells]
god_module_fan = 4
god_module_min_fan_in = 4
god_module_min_fan_out = 4
god_module_min_lines = 0
god_module_min_units = 0
shotgun_blast_threshold = 5

Problem

from god_module.dep_a import run as load_user
from god_module.dep_b import run as charge_user
from god_module.dep_c import run as send_receipt
from god_module.dep_d import run as archive_receipt


def process_checkout() -> int:
    return load_user() + charge_user() + send_receipt() + archive_receipt()

Proposed fix

Split by dependency direction and cohesive responsibility.

def load_user(user_id: str) -> User:
    return users.find(user_id)


def charge_user(user: User) -> Receipt:
    return billing.charge(user)


def send_receipt(user: User, receipt: Receipt) -> None:
    mailer.send(user.email, receipt)


def archive_receipt(receipt: Receipt) -> None:
    archive.save(receipt)

Problem

import { run as loadUser } from "./dep_a";
import { run as chargeUser } from "./dep_b";
import { run as sendReceipt } from "./dep_c";
import { run as archiveReceipt } from "./dep_d";

export function processCheckout(): number {
  return loadUser() + chargeUser() + sendReceipt() + archiveReceipt();
}

Proposed fix

Extract smaller modules and route callers through a narrow public API.

function loadUser(userId: string): User {
  return users.find(userId);
}

function chargeUser(user: User): Receipt {
  return billing.charge(user);
}

function sendReceipt(user: User, receipt: Receipt): void {
  mailer.send(user.email, receipt);
}

function archiveReceipt(receipt: Receipt): void {
  archive.save(receipt);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.god_module]
enabled = true
action = "warning"
min_fan = 25 # combined incoming/outgoing fan before god-module risk
min_fan_in = 5 # importing modules required for a god module
min_fan_out = 10 # internal runtime imports required for a god module
min_lines = 150 # module lines required for a god module (or min_units)
min_units = 12 # top-level functions and classes required (or min_lines)
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Heavy Nested Function (heavy_nested_function)

What it is

An inner/nested function that grew large and logic-heavy — promote it to a top-level, testable function.

Why it's bad

Nested helpers hide important behavior and make tests awkward.

Example

The example is kept short, so it fires with these settings:

# The example stays short; at defaults the nested function needs more than 15 lines.
[smells]
max_nested_function_lines = 4

Problem

def import_customers(rows: list[dict[str, str]]) -> list[Customer]:
    def normalize(row: dict[str, str]) -> Customer:
        email = row["email"].strip().lower()
        status = row["status"].strip()
        plan = row["plan"].strip()
        region = row["region"].strip()
        return Customer(email, status, plan, region)

    return [normalize(row) for row in rows]

Proposed fix

Promote the helper to a top-level private function with direct tests.

def normalize_customer(row: Row) -> Customer:
    email = row["email"].strip().lower()
    status = row["status"].strip()
    plan = row["plan"].strip()
    region = row["region"].strip()
    return Customer(email, status, plan, region)


def import_customers(rows: Rows) -> Customers:
    return [normalize_customer(row) for row in rows]

Problem

function importCustomers(rows: Row[]): Customer[] {
  function normalize(row: Row): Customer {
    const email = row.email.trim().toLowerCase();
    const status = row.status.trim();
    const plan = row.plan.trim();
    const region = row.region.trim();
    return new Customer(email, status, plan, region);
  }

  return rows.map(normalize);
}

Proposed fix

Move the nested function to module scope or a small collaborator.

function normalizeCustomer(row: Row): Customer {
  const email = row.email.trim().toLowerCase();
  const status = row.status.trim();
  const plan = row.plan.trim();
  const region = row.region.trim();
  return new Customer(email, status, plan, region);
}

function importCustomers(rows: Row[]): Customer[] {
  return rows.map(normalizeCustomer);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.heavy_nested_function]
enabled = true
action = "warning"
max_lines = 15 # allowed function or nested-function length
skip_capturing = false # drop nested functions that read parent locals
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Large Class (large_class)

What it is

A class with too many methods/responsibilities — split it into focused classes. Related code-smell reference: Refactoring.Guru: Large Class.

Why it's bad

The class stops having a clear job and becomes a grab bag.

Example

The example is kept short, so it fires with these settings:

# The example stays short; at defaults a class needs more than 20 methods.
[smells]
max_class_methods = 3

Problem

class ReportManager:
    def build(self) -> Report:
        return build_report()

    def send(self) -> None:
        mailer.send()

    def archive(self) -> None:
        archive.save()

    def bill(self) -> Receipt:
        return billing.charge()

Proposed fix

Extract cohesive collaborators around loading, rendering, delivery, etc.

class ReportBuilder:
    def build(self) -> Report:
        return build_report()


class ReportMailer:
    def send(self, report: Report) -> None:
        mailer.send(report)


class ReportArchive:
    def save(self, report: Report) -> None:
        archive.save(report)


class ReportBilling:
    def charge(self, report: Report) -> Receipt:
        return billing.charge(report)

Problem

class ReportManager {
  build(): Report {
    return buildReport();
  }

  send(): void {
    mailer.send();
  }

  archive(): void {
    archive.save();
  }

  bill(): Receipt {
    return billing.charge();
  }
}

Proposed fix

Split the class by capability and keep a thin orchestration surface.

class ReportBuilder {
  build(): Report {
    return buildReport();
  }
}

class ReportMailer {
  send(report: Report): void {
    mailer.send(report);
  }
}

class ReportArchive {
  save(report: Report): void {
    archive.save(report);
  }
}

class ReportBilling {
  charge(report: Report): Receipt {
    return billing.charge(report);
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.large_class]
enabled = true
action = "warning"
max_methods = 20 # allowed methods before a class is large
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSYes
RustNo

Long Function (long_function)

What it is

Too many lines to grasp at once — extract cohesive pieces. Related code-smell reference: Refactoring.Guru: Long Method.

Why it's bad

The function becomes a scroll instead of a unit you can hold in your head.

Example

Problem

def import_orders(rows: Rows) -> Orders:
    parsed = [parse_order(row) for row in rows]
    valid = [order for order in parsed if order.is_valid]
    enriched = [enrich(order) for order in valid]
    totals = [calculate_total(order) for order in enriched]
    discounts = [calculate_discount(order) for order in enriched]
    taxes = [calculate_tax(order) for order in enriched]
    save_orders(enriched)
    notify_import_complete(enriched)
    archive_totals(totals)
    archive_discounts(discounts)
    archive_taxes(taxes)
    return enriched

Proposed fix

Extract named chunks that each complete one step of the workflow.

def import_orders(rows: Rows) -> Orders:
    orders = _valid_orders(rows)
    enriched = [enrich(order) for order in orders]
    _persist_import(enriched)
    return enriched


def _valid_orders(rows: Rows) -> Orders:
    parsed = [parse_order(row) for row in rows]
    return [order for order in parsed if order.is_valid]


def _persist_import(orders: Orders) -> None:
    save_orders(orders)
    notify_import_complete(orders)
    archive_totals([calculate_total(order) for order in orders])

Problem

function importOrders(rows: Rows): Orders {
  const parsed = rows.map(parseOrder);
  const valid = parsed.filter((order) => order.isValid);
  const enriched = valid.map(enrich);
  const totals = enriched.map(calculateTotal);
  const discounts = enriched.map(calculateDiscount);
  const taxes = enriched.map(calculateTax);
  saveOrders(enriched);
  notifyImportComplete(enriched);
  archiveTotals(totals);
  archiveDiscounts(discounts);
  archiveTaxes(taxes);
  return enriched;
}

Proposed fix

Extract cohesive helper functions and keep the orchestration readable.

function importOrders(rows: Rows): Orders {
  const orders = validOrders(rows);
  const enriched = orders.map(enrich);
  persistImport(enriched);
  return enriched;
}

function validOrders(rows: Rows): Orders {
  const parsed = rows.map(parseOrder);
  return parsed.filter((order) => order.isValid);
}

function persistImport(orders: Orders): void {
  saveOrders(orders);
  notifyImportComplete(orders);
  archiveTotals(orders.map(calculateTotal));
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.long_function]
enabled = true
action = "warning"
max_lines = 50 # allowed function or nested-function length
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • Ruff: PLR0915
  • ESLint: max-lines-per-function

Long Parameter List (long_parameter_list)

What it is

Too many parameters — group related ones into an object, or the function is doing too much. Related code-smell reference: Refactoring.Guru: Long Parameter List.

Why it's bad

The call contract becomes noisy and easy to pass in the wrong order.

Example

Problem

def search(term: str, page: int, per_page: int, sort: str) -> SearchResults:
    return index.find(term, page, per_page, sort)

Proposed fix

Group related parameters into a dataclass or keyword-only options object.

from dataclasses import dataclass


@dataclass(frozen=True)
class SearchQuery:
    term: str
    page: int
    per_page: int
    sort: str


def search(query: SearchQuery) -> SearchResults:
    return index.find(query)

Problem

function search(term: string, page: number, perPage: number, sort: string): SearchResults {
  return index.find(term, page, perPage, sort);
}

Proposed fix

Use an options object or domain type instead of positional arguments.

interface SearchQuery {
  term: string;
  page: number;
  perPage: number;
  sort: string;
}

function search(query: SearchQuery): SearchResults {
  return index.find(query);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.long_parameter_list]
enabled = true
action = "warning"
max_params = 5 # allowed parameters before flagging
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • Ruff: PLR0913
  • ESLint: max-params

Narrating Code (narrating_code)

What it is

A function is packed with step-by-step narration or commented-out code — prefer clearer names or extracted helpers, delete dead code, and keep comments for why.

Why it's bad

The prose becomes a second implementation that readers must keep in sync with the code.

Example

Problem

def activate_plan(account: Account) -> None:
    # Load the account owner.
    owner = account.owner
    # Load the billing profile.
    billing = owner.billing_profile
    # Check whether billing is enabled.
    if not billing.enabled:
        return
    # Load the selected plan.
    plan = billing.selected_plan
    # Activate the selected plan.
    account.activate(plan)
    # Notify the owner.
    notify_owner(owner, plan)

Proposed fix

Rename values and extract helper functions so the code says what the comments were saying.

def activate_plan(account: Account) -> None:
    owner = account.owner
    billing = owner.billing_profile
    if not billing.enabled:
        return
    _activate_selected_plan(account, owner, billing)


def _activate_selected_plan(
    account: Account,
    owner: Owner,
    billing: BillingProfile,
) -> None:
    plan = billing.selected_plan
    account.activate(plan)
    notify_owner(owner, plan)

Problem

function activatePlan(account: Account): void {
  // Load the account owner.
  const owner = account.owner;
  // Load the billing profile.
  const billing = owner.billingProfile;
  // Check whether billing is enabled.
  if (!billing.enabled) {
    return;
  }
  // Load the selected plan.
  const plan = billing.selectedPlan;
  // Activate the selected plan.
  account.activate(plan);
  // Notify the owner.
  notifyOwner(owner, plan);
}

Proposed fix

Extract named predicates/helpers and keep comments for constraints or rationale.

function activatePlan(account: Account): void {
  const owner = account.owner;
  const billing = owner.billingProfile;
  if (!billing.enabled) {
    return;
  }
  activateSelectedPlan(account, owner, billing);
}

function activateSelectedPlan(
  account: Account,
  owner: Owner,
  billing: BillingProfile,
): void {
  const plan = billing.selectedPlan;
  account.activate(plan);
  notifyOwner(owner, plan);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.narrating_code]
enabled = true
action = "warning"
min_comment_lines = 5 # detector-specific threshold
max_comment_ratio_percent = 30 # detector-specific threshold
min_function_lines = 12 # shorter functions are flagged only for commented-out code
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Coupling and Cohesion

Data Clump (data_clump)

What it is

The same group of values is passed together through many functions — bundle them into one object or typed structure. Related code-smell reference: Refactoring.Guru: Data Clumps.

Why it's bad

The same bundle has to be kept in sync everywhere it travels.

Example

Problem

def format_label(street: str, city: str, zip_code: str, country: str) -> str:
    return f"{street}, {city} {zip_code}, {country}"


def shipping_zone(street: str, city: str, zip_code: str, country: str) -> str:
    return zones.lookup(street, city, zip_code, country)


def is_deliverable(street: str, city: str, zip_code: str, country: str) -> bool:
    return carriers.covers(street, city, zip_code, country)

Proposed fix

Introduce a dataclass, TypedDict, or domain object for the repeated fields.

from dataclasses import dataclass


@dataclass(frozen=True)
class Address:
    street: str
    city: str
    zip_code: str
    country: str


def format_label(address: Address) -> str:
    return f"{address.street}, {address.city} {address.zip_code}, {address.country}"


def shipping_zone(address: Address) -> str:
    return zones.lookup(address)


def is_deliverable(address: Address) -> bool:
    return carriers.covers(address)

Problem

function formatLabel(street: string, city: string, zipCode: string, country: string): string {
  return `${street}, ${city} ${zipCode}, ${country}`;
}

function shippingZone(street: string, city: string, zipCode: string, country: string): string {
  return zones.lookup(street, city, zipCode, country);
}

function isDeliverable(street: string, city: string, zipCode: string, country: string): boolean {
  return carriers.covers(street, city, zipCode, country);
}

Proposed fix

Introduce an interface or value object and pass that object through the API.

interface Address {
  street: string;
  city: string;
  zipCode: string;
  country: string;
}

function formatLabel(address: Address): string {
  return `${address.street}, ${address.city} ${address.zipCode}, ${address.country}`;
}

function shippingZone(address: Address): string {
  return zones.lookup(address);
}

function isDeliverable(address: Address): boolean {
  return carriers.covers(address);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.data_clump]
enabled = true
action = "warning"
min_fields = 4 # shared fields required for a data clump
min_occurrences = 3 # functions sharing the clump before flagging
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Divergent Change (divergent_change)

What it is

One module gets edited for many unrelated reasons — it has too many responsibilities; split it along those axes. Related code-smell reference: Refactoring.Guru: Divergent Change.

Why it's bad

One file starts changing for unrelated reasons, so fixes get tangled.

Example

Problem

class InvoiceReport:
    def render_header(self) -> str:
        return self.title.upper()

    def calculate_total(self) -> float:
        return self.subtotal + self.tax

    def save_archive(self) -> None:
        archive.write(self.path)

Proposed fix

Split the module by reason-to-change: presentation, pricing, persistence, etc.

class InvoiceRenderer:
    def render_header(self, invoice: Invoice) -> str:
        return invoice.title.upper()


class InvoiceTotals:
    def calculate_total(self, invoice: Invoice) -> float:
        return invoice.subtotal + invoice.tax


class InvoiceArchive:
    def save(self, invoice: Invoice) -> None:
        archive.write(invoice.path)

Problem

class InvoiceReport {
  renderHeader(): string {
    return this.title.toUpperCase();
  }

  calculateTotal(): number {
    return this.subtotal + this.tax;
  }

  saveArchive(): void {
    archive.write(this.path);
  }
}

Proposed fix

Move unrelated responsibilities into focused modules or services.

class InvoiceRenderer {
  renderHeader(invoice: Invoice): string {
    return invoice.title.toUpperCase();
  }
}

class InvoiceTotals {
  calculateTotal(invoice: Invoice): number {
    return invoice.subtotal + invoice.tax;
  }
}

class InvoiceArchive {
  save(invoice: Invoice): void {
    archive.write(invoice.path);
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.divergent_change]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Divergent Abstraction (divergent_abstraction)

What it is

An abstraction has only two implementations that grow in different directions — remove the forced common type or narrow it to genuine shared behavior.

Why it's bad

A forced common type couples two implementations whose real responsibilities no longer match.

Example

Problem

from typing import Protocol


class Worker(Protocol):
    def run(self): ...


class FileWorker(Worker):
    def run(self): ...
    def open_file(self): ...
    def rotate_file(self): ...


class QueueWorker(Worker):
    def run(self): ...
    def reserve_job(self): ...
    def acknowledge_job(self): ...

Proposed fix

Narrow the Protocol to genuine shared behavior, or use the concrete types directly.

class FileWorker:
    def open_file(self): ...
    def rotate_file(self): ...


class QueueWorker:
    def reserve_job(self): ...
    def acknowledge_job(self): ...

Problem

abstract class Worker {
  abstract run(): void;
}

class FileWorker extends Worker {
  run() {}
  openFile() {}
  rotateFile() {}
}

class QueueWorker extends Worker {
  run() {}
  reserveJob() {}
  acknowledgeJob() {}
}

Proposed fix

Narrow the interface to genuine shared behavior, or use the concrete types directly.

class FileWorker {
  openFile() {}
  rotateFile() {}
}

class QueueWorker {
  reserveJob() {}
  acknowledgeJob() {}
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.divergent_abstraction]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Feature Envy (feature_envy)

What it is

A method uses another object's data more than its own — move it onto the class that owns that data. Related code-smell reference: Refactoring.Guru: Feature Envy.

Why it's bad

Logic sits next to the wrong data, so edits keep reaching through a foreign object.

Example

Problem

class Account:
    balance: int
    fee_rate: int
    limit: int


class BillingPolicy:
    name: str

    def fee_for(self, account: Account) -> tuple[str, int]:
        fee = account.balance + account.fee_rate + account.limit
        return self.name, fee

Proposed fix

Move the behavior onto the object that owns most of the data.

class Account:
    balance: int
    fee_rate: int
    limit: int

    def billing_fee(self) -> int:
        return self.balance + self.fee_rate + self.limit


class BillingPolicy:
    name: str

    def fee_for(self, account: Account) -> tuple[str, int]:
        return self.name, account.billing_fee()

Problem

class Account {
  balance = 0;
  feeRate = 0;
  limit = 0;
}

class BillingPolicy {
  name = "standard";

  feeFor(account: Account): [string, number] {
    const fee = account.balance + account.feeRate + account.limit;
    return [this.name, fee];
  }
}

Proposed fix

Put the calculation on the owning class/module or expose a narrow query method.

class Account {
  balance = 0;
  feeRate = 0;
  limit = 0;

  billingFee(): number {
    return this.balance + this.feeRate + this.limit;
  }
}

class BillingPolicy {
  name = "standard";

  feeFor(account: Account): [string, number] {
    return [this.name, account.billingFee()];
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.feature_envy]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Inappropriate Intimacy (inappropriate_intimacy)

What it is

Two classes each reach into the other's internals, so neither can change independently — narrow the shared surface or merge them. Related code-smell reference: Refactoring.Guru: Inappropriate Intimacy.

Why it's bad

Two classes know too much about each other's internals, so refactors ripple.

Example

Problem

class Account:
    def __init__(self) -> None:
        self._token = "secret"
        self._salt = "pepper"


def audit_account() -> str:
    account: Account = Account()
    return account._token + account._salt

Proposed fix

Add a public method on the collaborator or merge the coupled objects.

class Account:
    def __init__(self) -> None:
        self._token = "secret"
        self._salt = "pepper"

    def audit_key(self) -> str:
        return self._token + self._salt


def audit_account() -> str:
    account = Account()
    return account.audit_key()

Problem

class Account {
  _token = "secret";
  _salt = "pepper";
}

function auditAccount(): string {
  const account: Account = new Account();
  return account._token + account._salt;
}

Proposed fix

Expose a narrow method/property instead of reaching into internals.

class Account {
  private token = "secret";
  private salt = "pepper";

  auditKey(): string {
    return this.token + this.salt;
  }
}

function auditAccount(): string {
  const account = new Account();
  return account.auditKey();
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.inappropriate_intimacy]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Message Chain (message_chain)

What it is

A long a.b.c.d access chain couples the caller to a deep object graph (Law of Demeter) — ask the immediate collaborator instead. Related code-smell reference: Refactoring.Guru: Message Chains.

Why it's bad

Deep property walking couples the caller to the whole object graph.

Example

Problem

def country(order: Order) -> str:
    return order.customer.address.city.country.name

Proposed fix

Ask the nearest object for the answer through a method or property.

class Order:
    def shipping_country(self) -> str:
        return self.customer.shipping_country()


class Customer:
    def shipping_country(self) -> str:
        return self.address.country_name()


class Address:
    def country_name(self) -> str:
        return self.city.country_name()


class City:
    def country_name(self) -> str:
        return self.country.name


def country(order: Order) -> str:
    return order.shipping_country()

Problem

function country(order: Order): string {
  return order.customer.address.city.country.name;
}

Proposed fix

Expose a query on the immediate collaborator instead of chaining internals.

class Order {
  shippingCountry(): string {
    return this.customer.shippingCountry();
  }
}

class Customer {
  shippingCountry(): string {
    return this.address.countryName();
  }
}

class Address {
  countryName(): string {
    return this.city.countryName();
  }
}

class City {
  countryName(): string {
    return this.country.name;
  }
}

function country(order: Order): string {
  return order.shippingCountry();
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.message_chain]
enabled = true
action = "warning"
max_depth = 4 # allowed depth before flagging
ignore_roots = [] # chain roots never reported, e.g. a generated API client
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Refused Bequest (refused_bequest)

What it is

A subclass inherits methods/fields it doesn't use or stubs out — the inheritance is wrong; prefer composition. Related code-smell reference: Refactoring.Guru: Refused Bequest.

Why it's bad

Inheritance promises behavior the subclass does not actually want.

Example

Problem

class Report:
    def export_pdf(self) -> bytes:
        return render_pdf(self)

    def send_email(self) -> None:
        mailer.send(self)


class ReadOnlyReport(Report):
    def export_pdf(self) -> bytes:
        raise NotImplementedError

    def send_email(self) -> None:
        raise NotImplementedError

Proposed fix

Prefer composition or split the base class into smaller capabilities.

class ReadOnlyReport:
    def export_pdf(self) -> bytes:
        return render_pdf(self)

Problem

class Report {
  exportPdf(): Uint8Array {
    return renderPdf(this);
  }

  sendEmail(): void {
    mailer.send(this);
  }
}

class ReadOnlyReport extends Report {
  exportPdf(): Uint8Array {
    throw new Error("not supported");
  }

  sendEmail(): void {
    throw new Error("not supported");
  }
}

Proposed fix

Use composition or narrower interfaces instead of a broad base class.

class ReadOnlyReport implements ExportableReport {
  exportPdf(): Uint8Array {
    return renderPdf(this);
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.refused_bequest]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Shotgun Surgery Hazard (shotgun_surgery_hazard)

What it is

A symbol so widely depended-on that one change ripples across many modules — a blast-radius hotspot. Related code-smell reference: Refactoring.Guru: Shotgun Surgery.

Why it's bad

One edit fans out to many dependents.

Example

Problem

TAX_RATE = 0.2


def calculate(amount: float) -> float:
    return amount * TAX_RATE

Proposed fix

Separate stable interfaces from volatile implementation details.

class TaxPolicy:
    def __init__(self, rate: float) -> None:
        self.rate = rate

    def calculate(self, amount: float) -> float:
        return amount * self.rate

Problem

export const TAX_RATE = 0.2;

export function calculate(amount: number): number {
  return amount * TAX_RATE;
}

Proposed fix

Reduce fan-in by splitting policy from shared utility shape.

class TaxPolicy {
  constructor(private readonly rate: number) {}

  calculate(amount: number): number {
    return amount * this.rate;
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.shotgun_surgery_hazard]
enabled = true
action = "warning"
min_blast = 4 # dependent modules before shotgun-surgery risk
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Sibling Helper Redefinition (sibling_helper_redefinition)

What it is

The same small helper — same name, same parameter count, near-identical body — is written again in another module instead of imported.

Why it's bad

A fix lands in one copy and the others drift; a reviewer reading one file cannot know the helper already exists.

Example

Problem

def message_of(error):
    return str(error.args[0]) if error.args else repr(error)

Proposed fix

Keep one definition in a shared module and import it where the copies were.

import logging

from example import message_of


def log_failure(failure):
    logging.error(message_of(failure))

Problem

export function messageOf(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

Proposed fix

Export one definition from a shared module and import it where the copies were.

import { messageOf } from "./example";

export function logFailure(failure: unknown): void {
  console.error(messageOf(failure));
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.sibling_helper_redefinition]
enabled = true
action = "warning"
min_similarity = 80 # percent of matching body lexemes required between copies
min_lexemes = 8 # lexeme codes a helper body needs before it is compared
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Type and Data Modeling

Implicit Schema (implicit_schema)

What it is

A dict/object accessed by many string keys — an unwritten schema; model it as a typed structure.

Why it's bad

Stringly typed payloads drift silently when the shape changes.

Example

Problem

def create_user(payload: dict[str, object]) -> User:
    email = payload["email"]
    plan = payload["plan"]
    active = payload["active"]
    seats = payload["seats"]
    return users.create(email, plan, active, seats)

Proposed fix

Model the payload with a dataclass, TypedDict, or Pydantic model.

from dataclasses import dataclass


@dataclass(frozen=True)
class UserPayload:
    email: str
    plan: str
    active: bool
    seats: int


def create_user(payload: UserPayload) -> User:
    return users.create(payload)

Problem

function createUser(payload: Record<string, unknown>): User {
  const email = payload["email"];
  const plan = payload["plan"];
  const active = payload["active"];
  const seats = payload["seats"];
  return users.create(email, plan, active, seats);
}

Proposed fix

Replace loose records with an interface or validated schema.

interface UserPayload {
  email: string;
  plan: string;
  active: boolean;
  seats: number;
}

function createUser(payload: UserPayload): User {
  return users.create(payload);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.implicit_schema]
enabled = true
action = "warning"
min_keys = 4 # string keys on one object before implicit-schema risk
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Loose Typing (loose_typing)

What it is

A public signature or class field leans on vague types (Any/untyped/overly broad) — tighten annotations so callers and tools know the contract. Casts inside a body belong to type_escape_cast.

Why it's bad

Weak types make invalid states look valid until runtime.

Example

Flags only direct escape hatches (any, Any, as any casts).

Problem

from typing import Any


def notify_contact(raw_contact: Any) -> None:
    if raw_contact["active"]:
        mailer.send(raw_contact["email"])

Proposed fix

Use a dataclass or another concrete model so callers pass named fields instead of loose keys. A mapping with a typed value such as dict[str, int] is reported only at high strictness. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.

from dataclasses import dataclass


@dataclass(frozen=True)
class UserContact:
    email: str
    active: bool


def notify(user: UserContact) -> None:
    if user.active:
        mailer.send(user.email)

Flags escape hatches plus schema-erasing maps and records: bare dict, dict[str, object], Record<string, unknown>.

Problem

from typing import Any


def notify_contact(contact: dict[str, Any]) -> None:
    if contact["active"]:
        mailer.send(contact["email"])

Proposed fix

Use a dataclass or another concrete model so callers pass named fields instead of loose keys. A mapping with a typed value such as dict[str, int] is reported only at high strictness. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.

from dataclasses import dataclass


@dataclass(frozen=True)
class UserContact:
    email: str
    active: bool


def notify(user: UserContact) -> None:
    if user.active:
        mailer.send(user.email)

Flags everything in medium, primitive-only collections and typed-value maps (dict[str, int]), bare unknown params and returns, and shallow aliases.

Problem

EmailAddress = str


def notify_all(addresses: list[EmailAddress]) -> None:
    for address in addresses:
        mailer.send(address)

Proposed fix

Use a dataclass or another concrete model so callers pass named fields instead of loose keys. A mapping with a typed value such as dict[str, int] is reported only at high strictness. Do not fix this by creating a shallow alias such as UserPayload = dict[str, Any] or UserId = str.

from dataclasses import dataclass


@dataclass(frozen=True)
class UserContact:
    email: str
    active: bool


def notify(user: UserContact) -> None:
    if user.active:
        mailer.send(user.email)

Flags only direct escape hatches (any, Any, as any casts).

Problem

function notifyContact(rawContact: any): void {
  if (rawContact.active) {
    mailer.send(rawContact.email);
  }
}

Proposed fix

Replace any with an interface, unknown plus narrowing, or a schema-derived type. Type-guard subjects and predicates are never reported, nor callbacks whose result is unknown; bare unknown params and returns only at high strictness. This rule reads signatures and class fields only: as any and other casts inside a body are type_escape_cast findings. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.

interface User {
  email: string;
  active: boolean;
}

function notify(user: User): void {
  if (user.active) {
    mailer.send(user.email);
  }
}

Flags escape hatches plus schema-erasing maps and records: bare dict, dict[str, object], Record<string, unknown>.

Problem

function notifyContact(contact: Record<string, any>): void {
  if (contact.active) {
    mailer.send(contact.email);
  }
}

Proposed fix

Replace any with an interface, unknown plus narrowing, or a schema-derived type. Type-guard subjects and predicates are never reported, nor callbacks whose result is unknown; bare unknown params and returns only at high strictness. This rule reads signatures and class fields only: as any and other casts inside a body are type_escape_cast findings. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.

interface User {
  email: string;
  active: boolean;
}

function notify(user: User): void {
  if (user.active) {
    mailer.send(user.email);
  }
}

Flags everything in medium, primitive-only collections and typed-value maps (dict[str, int]), bare unknown params and returns, and shallow aliases.

Problem

type EmailAddress = string;

function notifyAll(addresses: EmailAddress[]): void {
  for (const address of addresses) {
    mailer.send(address);
  }
}

Proposed fix

Replace any with an interface, unknown plus narrowing, or a schema-derived type. Type-guard subjects and predicates are never reported, nor callbacks whose result is unknown; bare unknown params and returns only at high strictness. This rule reads signatures and class fields only: as any and other casts inside a body are type_escape_cast findings. Do not fix this by creating a shallow alias such as type UserPayload = Record<string, any> or type UserId = string.

interface User {
  email: string;
  active: boolean;
}

function notify(user: User): void {
  if (user.active) {
    mailer.send(user.email);
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "low"

Flags only direct escape hatches (any, Any, as any casts).

[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "medium"

Flags escape hatches plus schema-erasing maps and records: bare dict, dict[str, object], Record<string, unknown>.

[smells.<lang>.rules.loose_typing]
enabled = true
action = "warning"
strictness = "high"

Flags everything in medium, primitive-only collections and typed-value maps (dict[str, int]), bare unknown params and returns, and shallow aliases.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Type Escape Cast (type_escape_cast)

What it is

A function body silences the type checker (as any, as unknown as T, a bare @ts-ignore or # type: ignore, cast(Any, …)) instead of giving the value a type — the types and the code disagree and nobody looked. Body casts belong to this rule; signatures belong to loose_typing.

Why it's bad

A cast or suppression in a body turns a type error into a runtime surprise: the checker complained, the code was silenced, and no reviewer saw why.

Example

Flags casts to the escape type (as any, cast(Any, …)) and bare @ts-ignore.

Problem

from typing import Any, cast


def order_total(raw: object) -> int:
    payload = cast(Any, raw)
    return sum(item["amount"] for item in payload["items"])

Proposed fix

Give the value a real type (a dataclass, TypedDict or validated model) or narrow it with isinstance. A # type: ignore that must stay names its error code, as in # type: ignore[attr-defined]. Casts in a TypeGuard function on its own subject are not reported. Signatures belong to loose_typing; this rule owns casts inside bodies.

def order_total(raw: object) -> int:
    payload = Order.model_validate(raw)
    return sum(item.amount for item in payload.items)

Also flags as unknown as T, bare @ts-expect-error, and # type: ignore without an error code.

Problem

def shipping_label(order: Order) -> str:
    return f"{order.parcel.carrier} {order.parcel.tracking_code}"  # type: ignore

Proposed fix

Give the value a real type (a dataclass, TypedDict or validated model) or narrow it with isinstance. A # type: ignore that must stay names its error code, as in # type: ignore[attr-defined]. Casts in a TypeGuard function on its own subject are not reported. Signatures belong to loose_typing; this rule owns casts inside bodies.

def order_total(raw: object) -> int:
    payload = Order.model_validate(raw)
    return sum(item.amount for item in payload.items)

Also flags non-null assertions on call results (find()!).

Problem

def primary_email(user: User) -> str:
    return user.primary_email.address  # type: ignore

Proposed fix

Give the value a real type (a dataclass, TypedDict or validated model) or narrow it with isinstance. A # type: ignore that must stay names its error code, as in # type: ignore[attr-defined]. Casts in a TypeGuard function on its own subject are not reported. Signatures belong to loose_typing; this rule owns casts inside bodies.

def order_total(raw: object) -> int:
    payload = Order.model_validate(raw)
    return sum(item.amount for item in payload.items)

Flags casts to the escape type (as any, cast(Any, …)) and bare @ts-ignore.

Problem

export async function orderTotal(res: Response): Promise<number> {
  const payload = (await res.json()) as any;
  return payload.items.reduce((sum: number, item: { amount: number }) => sum + item.amount, 0);
}

Proposed fix

Parse the value with a schema or narrow it with a type guard instead of as any or as unknown as T. A suppression that must stay is @ts-expect-error with a reason after it. as const and satisfies are never reported, nor are casts of the guarded subject inside a type-predicate function, casts with a rationale comment on their line or the line above, and (at high) non-null calls already checked by an enclosing condition, a has test or a length loop. The type_escape_strictness knob (strictness under [smells.rules.type_escape_cast]) adds non-null assertions on call results at high. Signatures belong to loose_typing; this rule owns casts inside bodies.

export async function orderTotal(res: Response): Promise<number> {
  const payload = OrderSchema.parse(await res.json());
  return payload.items.reduce((sum, item) => sum + item.amount, 0);
}

Also flags as unknown as T, bare @ts-expect-error, and # type: ignore without an error code.

Problem

export function shippingLabel(order: Order): string {
  const parcel = order as unknown as Parcel;
  return `${parcel.carrier} ${parcel.trackingCode}`;
}

Proposed fix

Parse the value with a schema or narrow it with a type guard instead of as any or as unknown as T. A suppression that must stay is @ts-expect-error with a reason after it. as const and satisfies are never reported, nor are casts of the guarded subject inside a type-predicate function, casts with a rationale comment on their line or the line above, and (at high) non-null calls already checked by an enclosing condition, a has test or a length loop. The type_escape_strictness knob (strictness under [smells.rules.type_escape_cast]) adds non-null assertions on call results at high. Signatures belong to loose_typing; this rule owns casts inside bodies.

export async function orderTotal(res: Response): Promise<number> {
  const payload = OrderSchema.parse(await res.json());
  return payload.items.reduce((sum, item) => sum + item.amount, 0);
}

Also flags non-null assertions on call results (find()!).

Problem

export function primaryEmail(user: User): string {
  return user.emails.find((email) => email.primary)!.address;
}

Proposed fix

Parse the value with a schema or narrow it with a type guard instead of as any or as unknown as T. A suppression that must stay is @ts-expect-error with a reason after it. as const and satisfies are never reported, nor are casts of the guarded subject inside a type-predicate function, casts with a rationale comment on their line or the line above, and (at high) non-null calls already checked by an enclosing condition, a has test or a length loop. The type_escape_strictness knob (strictness under [smells.rules.type_escape_cast]) adds non-null assertions on call results at high. Signatures belong to loose_typing; this rule owns casts inside bodies.

export async function orderTotal(res: Response): Promise<number> {
  const payload = OrderSchema.parse(await res.json());
  return payload.items.reduce((sum, item) => sum + item.amount, 0);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.type_escape_cast]
enabled = true
action = "warning"
strictness = "low"

Flags casts to the escape type (as any, cast(Any, …)) and bare @ts-ignore.

[smells.<lang>.rules.type_escape_cast]
enabled = true
action = "warning"
strictness = "medium"

Also flags as unknown as T, bare @ts-expect-error, and # type: ignore without an error code.

[smells.<lang>.rules.type_escape_cast]
enabled = true
action = "warning"
strictness = "high"

Also flags non-null assertions on call results (find()!).

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

External linter coverage

  • ESLint: @typescript-eslint/no-explicit-any
  • ESLint: @typescript-eslint/ban-ts-comment
  • Ruff: PGH003

Tuple Packing (tuple_packing)

What it is

Data passed as positional tuples whose fields aren't named — use a named structure so meaning is explicit.

Why it's bad

Positional bundles hide meaning and make the code brittle to reorderings.

Example

Problem

def summarize(user: User) -> tuple[str, str, int]:
    return user.name, user.email, user.seats

Proposed fix

Use a named tuple, dataclass, or object with explicit fields.

from dataclasses import dataclass


@dataclass(frozen=True)
class UserSummary:
    name: str
    email: str
    seats: int


def summarize(user: User) -> UserSummary:
    return UserSummary(name=user.name, email=user.email, seats=user.seats)

Problem

function summarize(user: User): [string, string, number] {
  return [user.name, user.email, user.seats];
}

Proposed fix

Use an interface/object instead of anonymous positional tuple data.

interface UserSummary {
  name: string;
  email: string;
  seats: number;
}

function summarize(user: User): UserSummary {
  return { name: user.name, email: user.email, seats: user.seats };
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.tuple_packing]
enabled = true
action = "warning"
max_tuple_return = 2 # allowed positional tuple items in a return
allow_homogeneous = true # allow same-primitive tuples of up to 4 items
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Defensive Fallback Soup (defensive_fallback)

What it is

Broad error handling and repeated empty defaults hide invalid states — validate the boundary and let unexpected failures remain visible.

Why it's bad

Broad catches plus empty defaults turn contract violations into plausible but wrong data.

Example

Problem

def load_payload(raw):
    try:
        names = raw.get("names") or []
        options = raw.get("options") or {}
        return names, options
    except:
        return [], {}

Proposed fix

Validate once at the boundary, catch specific failures, and preserve unexpected errors.

def load_payload(raw):
    try:
        return Payload.parse(raw)
    except InvalidPayload as error:
        raise UserInputError() from error

Problem

function loadPayload(raw: Input) {
  try {
    const names = raw.names || [];
    const options = raw.options || {};
    return { names, options };
  } catch {
    return { names: [], options: {} };
  }
}

Proposed fix

Parse once at the boundary, catch specific failures, and preserve unexpected errors.

function loadPayload(raw: unknown): Payload {
  return PayloadSchema.parse(raw);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.defensive_fallback]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

Swallowed Error Sentinel (swallowed_error_sentinel)

What it is

An error handler drops the error and returns a placeholder (null, [], false) or only logs — callers get a plausible value instead of a failure; rethrow or return a typed failure.

Why it's bad

A handler that turns any failure into null, an empty list or a log line hands callers a plausible value; the broken contract surfaces far from its cause, or never.

Example

Flags handlers that return an empty value (null, None, [], false), except around a single JSON parse.

Problem

def load_profile(profile_id):
    try:
        return client.profiles.get(profile_id)
    except Exception as error:
        log.warning("profile load failed: %s", error)
        return None

Proposed fix

Catch the specific exception you can handle, re-raise the rest with raise ... from error, or return a typed failure the caller must check.

def load_profile(profile_id):
    try:
        return client.profiles.get(profile_id)
    except ClientError as error:
        raise ProfileUnavailable(profile_id) from error

Also flags empty-value returns around a single JSON parse.

Problem

import json


def read_settings(text):
    try:
        return json.loads(text)
    except Exception:
        return None

Proposed fix

Catch the specific exception you can handle, re-raise the rest with raise ... from error, or return a typed failure the caller must check.

def load_profile(profile_id):
    try:
        return client.profiles.get(profile_id)
    except ClientError as error:
        raise ProfileUnavailable(profile_id) from error

Flags handlers that return an empty value (null, None, [], false), except around a single JSON parse.

Problem

async function loadProfile(id: string): Promise<Profile | null> {
  try {
    return await client.profiles.get(id);
  } catch (error) {
    console.warn("profile load failed", error);
    return null;
  }
}

Proposed fix

Rethrow, or return a typed failure ({ ok: false, error } or a Result) so the caller must handle it; keep a tryX(): T | null helper only where missing is the documented contract.

async function loadProfile(id: string): Promise<Result<Profile, LoadError>> {
  try {
    return ok(await client.profiles.get(id));
  } catch (error) {
    return err(toLoadError(error));
  }
}

Also flags empty-value returns around a single JSON parse.

Problem

function readSettings(text: string) {
  try {
    return JSON.parse(text);
  } catch {
    return undefined;
  }
}

Proposed fix

Rethrow, or return a typed failure ({ ok: false, error } or a Result) so the caller must handle it; keep a tryX(): T | null helper only where missing is the documented contract.

async function loadProfile(id: string): Promise<Result<Profile, LoadError>> {
  try {
    return ok(await client.profiles.get(id));
  } catch (error) {
    return err(toLoadError(error));
  }
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.swallowed_error_sentinel]
enabled = true
action = "warning"
strictness = "low"

Flags handlers that return an empty value (null, None, [], false), except around a single JSON parse.

[smells.<lang>.rules.swallowed_error_sentinel]
enabled = true
action = "warning"
strictness = "medium"

Also flags empty-value returns around a single JSON parse.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Mutation and State

Mutated Parameter (mutated_parameter)

What it is

The function mutates a caller's argument in place — a hidden side effect; return a new value instead.

Why it's bad

Mutating inputs hides side effects and makes call order matter.

Example

Problem

def add_item(items: list[Item], item: Item) -> list[Item]:
    items.append(item)
    return items

Proposed fix

Return a new collection or make the mutation explicit in the API name.

def add_item(items: list[Item], item: Item) -> list[Item]:
    next_items = list(items)
    next_items.append(item)
    return next_items

Problem

function addItem(items: Item[], item: Item): Item[] {
  items.push(item);
  return items;
}

Proposed fix

Return a copied value or make mutation an intentional method on an owner.

function addItem(items: Item[], item: Item): Item[] {
  return [...items, item];
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.mutated_parameter]
enabled = true
action = "warning"
include_attributes = false # also flag mutation of fields on parameters
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

External linter coverage

  • ESLint: no-param-reassign

Name Promise Mismatch (name_promise_mismatch)

What it is

A function named like a read (get, is, find, validate, ...) also writes state — rename it to say so or move the write to the caller.

Why it's bad

Callers trust a read-named function to be safe to call twice, cache, or skip; a hidden write breaks all three.

Example

Flags only database, network and scheduling writes.

Problem

def get_invoice(db: Database, invoice_id: str) -> Invoice | None:
    invoice = db.invoices.find(invoice_id)
    if invoice is None:
        db.invoices.insert({"id": invoice_id, "status": "draft"})
    return invoice

Proposed fix

Rename it to name the write (get_or_create_invoice), or return the data and let the caller write.

def get_or_create_invoice(db: Database, invoice_id: str) -> Invoice:
    invoice = db.invoices.find(invoice_id)
    if invoice is not None:
        return invoice
    return db.invoices.insert({"id": invoice_id, "status": "draft"})

Also flags mutating calls and assignments on parameters, own members and module-level names; skips caches and lazy initialisation.

Problem

class TaskQueue:
    def __init__(self) -> None:
        self.tasks: list[Task] = []

    def get_next_task(self) -> Task | None:
        next_task = self.tasks[0] if self.tasks else None
        self.tasks.remove(next_task)
        return next_task

Proposed fix

Rename it to name the write (get_or_create_invoice), or return the data and let the caller write.

def get_or_create_invoice(db: Database, invoice_id: str) -> Invoice:
    invoice = db.invoices.find(invoice_id)
    if invoice is not None:
        return invoice
    return db.invoices.insert({"id": invoice_id, "status": "draft"})

Also flags cache fills and lazy initialisation, and treats load and fetch names as reads.

Problem

class RateTable:
    def __init__(self) -> None:
        self.source: RateSource | None = None

    def get_source(self) -> RateSource:
        if self.source is None:
            self.source = RateSource()
        return self.source

Proposed fix

Rename it to name the write (get_or_create_invoice), or return the data and let the caller write.

def get_or_create_invoice(db: Database, invoice_id: str) -> Invoice:
    invoice = db.invoices.find(invoice_id)
    if invoice is not None:
        return invoice
    return db.invoices.insert({"id": invoice_id, "status": "draft"})

Flags only database, network and scheduling writes.

Problem

export async function getInvoice(db: Database, invoiceId: string): Promise<Invoice | null> {
  const invoice = await db.invoices.find(invoiceId);
  if (!invoice) {
    await db.invoices.insert({ id: invoiceId, status: "draft" });
  }
  return invoice;
}

Proposed fix

Rename it to name the write (getOrCreateInvoice), or return the data and let the caller write.

export async function getOrCreateInvoice(db: Database, invoiceId: string): Promise<Invoice> {
  const invoice = await db.invoices.find(invoiceId);
  if (invoice) {
    return invoice;
  }
  return db.invoices.insert({ id: invoiceId, status: "draft" });
}

Also flags mutating calls and assignments on parameters, own members and module-level names; skips caches and lazy initialisation.

Problem

export class TaskQueue {
  private tasks: Task[] = [];

  getNextTask(): Task | undefined {
    const next = this.tasks[0];
    this.tasks.splice(0, 1);
    return next;
  }
}

Proposed fix

Rename it to name the write (getOrCreateInvoice), or return the data and let the caller write.

export async function getOrCreateInvoice(db: Database, invoiceId: string): Promise<Invoice> {
  const invoice = await db.invoices.find(invoiceId);
  if (invoice) {
    return invoice;
  }
  return db.invoices.insert({ id: invoiceId, status: "draft" });
}

Also flags cache fills and lazy initialisation, and treats load and fetch names as reads.

Problem

const rates = new Map<string, number>();

export function getRate(currency: string): number {
  const known = rates.get(currency);
  if (known !== undefined) {
    return known;
  }
  const rate = currency.length / 10;
  rates.set(currency, rate);
  return rate;
}

Proposed fix

Rename it to name the write (getOrCreateInvoice), or return the data and let the caller write.

export async function getOrCreateInvoice(db: Database, invoiceId: string): Promise<Invoice> {
  const invoice = await db.invoices.find(invoiceId);
  if (invoice) {
    return invoice;
  }
  return db.invoices.insert({ id: invoiceId, status: "draft" });
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.name_promise_mismatch]
enabled = true
action = "warning"
strictness = "low"
read_prefixes = ["get", "is", "has", "find", "list", "validate", "check"] # name prefixes that promise a read
write_methods = ["insert", "patch", "upsert", "delete", "update", "set", "save", "commit", "post", "put"] # method names that count as writes on outside receivers (each language adds its own mutators)

Flags only database, network and scheduling writes.

[smells.<lang>.rules.name_promise_mismatch]
enabled = true
action = "warning"
strictness = "medium"
read_prefixes = ["get", "is", "has", "find", "list", "validate", "check"] # name prefixes that promise a read
write_methods = ["insert", "patch", "upsert", "delete", "update", "set", "save", "commit", "post", "put"] # method names that count as writes on outside receivers (each language adds its own mutators)

Also flags mutating calls and assignments on parameters, own members and module-level names; skips caches and lazy initialisation.

[smells.<lang>.rules.name_promise_mismatch]
enabled = true
action = "warning"
strictness = "high"
read_prefixes = ["get", "is", "has", "find", "list", "validate", "check"] # name prefixes that promise a read
write_methods = ["insert", "patch", "upsert", "delete", "update", "set", "save", "commit", "post", "put"] # method names that count as writes on outside receivers (each language adds its own mutators)

Also flags cache fills and lazy initialisation, and treats load and fetch names as reads.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Reassigned Parameter (reassigned_parameter)

What it is

A parameter is rebound to a new value inside the body — confusing; use a separate local.

Why it's bad

Rebinding a parameter muddies the original meaning of the value.

Example

Problem

def normalize(name: str) -> str:
    name = name.strip()
    return name.title()

Proposed fix

Introduce a local variable for the transformed value.

def normalize(name: str) -> str:
    cleaned_name = name.strip()
    return cleaned_name.title()

Problem

function normalize(name: string): string {
  name = name.trim();
  return name.toUpperCase();
}

Proposed fix

Use a separate const for each semantic step.

function normalize(name: string): string {
  const cleanedName = name.trim();
  return cleanedName.toUpperCase();
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.reassigned_parameter]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

External linter coverage

  • ESLint: no-param-reassign

Split Variable (split_variable)

What it is

One local is reassigned to mean different things at different points — use distinct, single-purpose bindings.

Why it's bad

A variable with multiple meanings is a trap for both readers and debuggers.

Example

Problem

def invoice_total(invoice: Invoice) -> float:
    result = sum(item.price for item in invoice.items)
    result = result - invoice.discount
    result = result + invoice.tax
    return result

Proposed fix

Use separate locals named for each meaning.

def invoice_total(invoice: Invoice) -> float:
    subtotal = sum(item.price for item in invoice.items)
    discounted_total = subtotal - invoice.discount
    return discounted_total + invoice.tax

Problem

function invoiceTotal(invoice: Invoice): number {
  let result = sum(invoice.items.map((item) => item.price));
  result = result - invoice.discount;
  result = result + invoice.tax;
  result = roundCurrency(result);
  return result;
}

Proposed fix

Prefer distinct const bindings for distinct concepts.

function invoiceTotal(invoice: Invoice): number {
  const subtotal = sum(invoice.items.map((item) => item.price));
  const discountedTotal = subtotal - invoice.discount;
  return discountedTotal + invoice.tax;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.split_variable]
enabled = true
action = "warning"
min_assigns = 2 # assignments to one local before flagging
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

Performance

Nested Loop (nested_loop)

What it is

A loop is nested directly or through a helper called inside a loop — work grows multiplicatively; combine passes or pre-index the data.

Why it's bad

Costs grow faster than the data and the code gets hard to flatten.

Example

Problem

def find_matches(users: list[User], orders: list[Order]) -> list[tuple[User, Order]]:
    matches = []
    for user in users:
        for order in orders:
            if order.user_id == user.id:
                matches.append((user, order))
    return matches

Proposed fix

Pre-index one side, combine passes, or use a clearer iterator pipeline.

def find_matches(users: list[User], orders: list[Order]) -> list[tuple[User, Order]]:
    orders_by_user = {order.user_id: order for order in orders}
    matches = []
    for user in users:
        order = orders_by_user.get(user.id)
        if order is not None:
            matches.append((user, order))
    return matches

Problem

function findMatches(users: User[], orders: Order[]): Array<[User, Order]> {
  const matches: Array<[User, Order]> = [];
  for (const user of users) {
    for (const order of orders) {
      if (order.userId === user.id) {
        matches.push([user, order]);
      }
    }
  }
  return matches;
}

Proposed fix

Build a lookup map or flatten the data before iterating.

function findMatches(users: User[], orders: Order[]): Array<[User, Order]> {
  const ordersByUser = indexOrdersByUser(orders);
  const matches: Array<[User, Order]> = [];
  for (const user of users) {
    const order = ordersByUser.get(user.id);
    if (order) {
      matches.push([user, order]);
    }
  }
  return matches;
}

function indexOrdersByUser(orders: Order[]): Map<string, Order> {
  const ordersByUser = new Map<string, Order>();
  for (const order of orders) {
    ordersByUser.set(order.userId, order);
  }
  return ordersByUser;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.nested_loop]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

N+1 Loop Call (n_plus_one_call)

What it is

An external-looking call runs once per loop item — prefer a bulk query/request or prefetch so work scales by batch, not item.

Why it's bad

A per-item call can explode runtime and hit the backend one request at a time.

Example

Problem

def load_profiles(api: ProfileApi, users: list[User]) -> list[Profile]:
    profiles = []
    for user in users:
        profiles.append(api.fetch(user.id))
    return profiles

Proposed fix

Batch-load or prefetch the related data before the loop.

def load_profiles(api: ProfileApi, users: list[User]) -> list[Profile]:
    ids = [user.id for user in users]
    profiles = api.fetch_many(ids)
    return [profiles[user_id] for user_id in ids]

Problem

function loadProfiles(api: ProfileApi, users: User[]): Profile[] {
  const profiles = [];
  for (const user of users) {
    profiles.push(api.fetch(user.id));
  }
  return profiles;
}

Proposed fix

Use a bulk endpoint/query or prefetch into a map before rendering.

async function loadProfiles(api: ProfileApi, users: User[]): Promise<Profile[]> {
  const ids = users.map((user) => user.id);
  const profiles = await api.fetchMany(ids);
  return ids.map((id) => profiles[id]);
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.n_plus_one_call]
enabled = true
action = "warning"
callees = [] # globs for base.method or plain calls that hit a backend
max_constant_bound = 10 # loops with at most this literal trip count are skipped
await_in_loop = false # also flag awaited per-item calls on outside receivers
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo

Repeated Iteration (repeated_iteration)

What it is

The same collection gets three or more passes, two loops, or two passes where one folds it (reduce, sum, filter(..).length), in one scope with nothing forcing them apart — fuse the passes so the data is scanned once. Field projections, rendered lists and Set/Map indexes built from a pass do not count.

Why it's bad

The same collection gets scanned over and over when one pass would do.

Example

Problem

def summarize(items: Items) -> Summary:
    has_items = any(items)
    total = sum(items)
    return Summary(has_items=has_items, total=total)

Proposed fix

Fuse compatible passes or cache the intermediate result.

def summarize(items: Items) -> Summary:
    has_items = False
    total = 0
    for item in items:
        has_items = True
        total += item
    return Summary(has_items=has_items, total=total)

Problem

function summarize(items: Items): Summary {
  const paid = items.filter((item) => item.paid).length;
  const total = items.reduce((sum, item) => sum + item.price, 0);
  return { paid, total };
}

Proposed fix

Combine loops when the operations share the same traversal.

function summarize(items: Items): Summary {
  let paid = 0;
  let total = 0;
  for (const item of items) {
    if (item.paid) {
      paid += 1;
    }
    total += item.price;
  }
  return { paid, total };
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.repeated_iteration]
enabled = true
action = "warning"
include_tests = false # also flag test code and assertion arguments
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Sort In Loop (sort_in_loop)

What it is

A collection is sorted inside a loop — hoist sorting or maintain ordered data to avoid repeated O(n log n) work.

Why it's bad

Repeated sorts turn a small loop into a surprisingly expensive one.

Example

Problem

def grouped_names(groups: Groups) -> Names:
    names = []
    for group in groups:
        names.extend(group.names)
        names.sort()
    return names

Proposed fix

Sort once before the loop or keep data ordered as it is built.

def grouped_names(groups: Groups) -> Names:
    names = []
    for group in groups:
        names.extend(group.names)
    names.sort()
    return names

Problem

function groupedNames(groups: Groups, names: string[]): string[] {
  const result: string[] = [];
  for (const group of groups) {
    names.sort();
    result.push(group.name);
  }
  return result;
}

Proposed fix

Hoist sorting out of the loop or maintain an ordered structure.

function groupedNames(groups: Groups): string[] {
  const names: string[] = [];
  for (const group of groups) {
    names.push(...group.names);
  }
  names.sort();
  return names;
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.sort_in_loop]
enabled = true
action = "warning"
# This detector has no extra threshold knobs.
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Other

Dependency Reimplementation (dependency_reimplementation)

What it is

A small helper re-writes a utility that a declared dependency or the standard library already provides — call the provider instead of keeping a private copy.

Why it's bad

Each private copy of a stock utility is one more thing to test and maintain, and copies drift: one swaps the bounds, another drops the last chunk.

Example

Problem

def chunks(items, size):
    return [items[start : start + size] for start in range(0, len(items), size)]


print(chunks(["a", "b", "c"], 2))

Proposed fix

Call the standard-library or declared-package function the message names. List in-house utility packages in providers so their functions count too.

from itertools import batched

print(list(batched(["a", "b", "c"], 2)))

Problem

// package.json lists es-toolkit
function unique<T>(items: T[]): T[] {
  return Array.from(new Set(items));
}

console.log(unique(["a", "a", "b"]));

Proposed fix

Import the function the message names from the package package.json already lists. List in-house utility packages in providers so they count too.

import { uniq } from "es-toolkit";

console.log(uniq(["a", "a", "b"]));

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.dependency_reimplementation]
enabled = true
action = "warning"
providers = [] # in-house packages whose same-named functions count as providers
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Fixture Echo Assertion (fixture_echo_assertion)

What it is

A test assertion cannot fail: both sides are the same expression, equal literals, or a field read from a fixture and its unchanged alias; or, with include_mock_only, the test only checks that a mock it programmed was called — assert on what the code under test produced.

Why it's bad

An assertion that compares a value with itself passes whatever the code under test does, so the test reports coverage it does not have.

Example

Problem

def rename(label: str) -> str:
    return label.strip()


def test_keeps_label() -> None:
    label = rename(" box ")
    assert label == label

Proposed fix

Compare the result of the call under test with an independently written expected value.

def rename(label: str) -> str:
    return label.strip()


def test_keeps_label() -> None:
    assert rename(" box ") == "box"

Problem

import assert from "node:assert/strict";
import test from "node:test";

function rename(label: string): string {
  return label.trim();
}

test("keeps the label", () => {
  const label = rename(" box ");
  assert.equal(label, label);
});

Proposed fix

Pass the result of the call under test to expect and an independently written value to the matcher; for mocks, also assert on the value the subject returned.

import assert from "node:assert/strict";
import test from "node:test";

function rename(label: string): string {
  return label.trim();
}

test("keeps the label", () => {
  assert.equal(rename(" box "), "box");
});

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.fixture_echo_assertion]
enabled = true
action = "warning"
include_mock_only = false # also flag tests whose only checks are was-called checks on mocks they programmed
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Mocked Subject (mocked_subject)

What it is

A test file mocks the module its subject under test is imported from, so the test exercises the double instead of the code — mock collaborators, keep the subject real.

Why it's bad

A test that replaces the module it claims to test checks the double's canned answer; it stays green when the real code breaks.

Example

Flags a mock whose module resolves to the import a test calls its subject through.

Problem

from unittest.mock import patch

from shop import pricing


@patch("shop.pricing.quote", return_value=42)
def test_quote(mocked):
    assert pricing.quote(2) == 42

Proposed fix

Patch the collaborators the subject calls, or patch with wraps= to keep the real function.

from shop import pricing


def test_quote(mocker):
    mocker.patch("shop.pricing.fetch_rate", return_value=2)
    assert pricing.quote(2) == 4

Also flags a mocked module named like the test file, and patches of an attribute on an imported object the test calls.

Problem

from unittest.mock import patch

from shop.pricing import Pricing


def test_quote():
    with patch.object(Pricing, "rate", return_value=2):
        assert Pricing().quote(2) == 4

Proposed fix

Patch the collaborators the subject calls, or patch with wraps= to keep the real function.

from shop import pricing


def test_quote(mocker):
    mocker.patch("shop.pricing.fetch_rate", return_value=2)
    assert pricing.quote(2) == 4

Flags a mock whose module resolves to the import a test calls its subject through.

Problem

import { quote } from "./pricing";

vi.mock("./pricing", () => ({ quote: vi.fn(() => 42) }));

it("quotes", () => {
  expect(quote({ qty: 2 })).toBe(42);
});

Proposed fix

Mock the collaborators the subject imports, or keep the real module with importActual / importOriginal.

import { quote } from "./pricing";

vi.mock("./rates", () => ({ fetchRate: vi.fn(() => 1.5) }));

it("quotes", () => {
  expect(quote({ qty: 2 })).toBe(3);
});

Also flags a mocked module named like the test file, and patches of an attribute on an imported object the test calls.

Problem

// pricing.example.test.ts: the mocked module shares the test file's name.
import { render } from "./receipt";

vi.mock("./pricing");

it("renders the receipt", () => {
  expect(render({ qty: 2 })).toContain("4");
});

Proposed fix

Mock the collaborators the subject imports, or keep the real module with importActual / importOriginal.

import { quote } from "./pricing";

vi.mock("./rates", () => ({ fetchRate: vi.fn(() => 1.5) }));

it("quotes", () => {
  expect(quote({ qty: 2 })).toBe(3);
});

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.mocked_subject]
enabled = true
action = "warning"
strictness = "medium"

Flags a mock whose module resolves to the import a test calls its subject through.

[smells.<lang>.rules.mocked_subject]
enabled = true
action = "warning"
strictness = "high"

Also flags a mocked module named like the test file, and patches of an attribute on an imported object the test calls.

Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Redundant Wrapper Chain (redundant_wrapper_chain)

What it is

A local function forwards arguments unchanged without adding behavior — remove the wrapper or allow a deliberate depth in configuration.

Why it's bad

An unchanged forwarding layer adds indirection without behavior and makes navigation harder.

Example

Problem

def normalize(value: int) -> int:
    return value + 1


def middle(value: int) -> int:
    return normalize(value)


def outer(value: int) -> int:
    return middle(value)


result = outer(2)

Proposed fix

Remove pass-through functions, or raise max_depth when a deliberate boundary is worth keeping.

def normalize(value: int) -> int:
    return value + 1


result = normalize(2)

Problem

function normalize(value: number): number {
  return value + 1;
}

function middle(value: number): number {
  return normalize(value);
}

function outer(value: number): number {
  return middle(value);
}

const result = outer(2);
console.log(result);

Proposed fix

Remove pass-through functions, or raise max_depth when a deliberate boundary is worth keeping.

function normalize(value: number): number {
  return value + 1;
}

const result = normalize(2);
console.log(result);

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.redundant_wrapper_chain]
enabled = true
action = "warning"
max_depth = 0 # allowed depth before flagging
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Shipped Stub (stub_shipped)

What it is

A production function whose whole body is a placeholder (pass, raise NotImplementedError, a "not implemented" throw) — it fails the first caller who reaches it.

Why it's bad

Scaffolding written for later ships as a function that only fails, and the first user on that path hits the error at runtime. Unreferenced stubs are also reported as dead code. Keep deliberate scaffolding folders out with [smells] exclude.

Example

Problem

def export_ledger(org_id: str) -> bytes:
    raise NotImplementedError


report = export_ledger("acme")

Proposed fix

Implement the function, delete it with its callers, or declare it with @abstractmethod on an ABC/Protocol.

def export_ledger(org_id: str) -> bytes:
    return f"ledger,{org_id}\n".encode()


report = export_ledger("acme")

Problem

export async function exportLedger(orgId: string): Promise<Blob> {
  // TODO: implement CSV export
  throw new Error("not implemented");
}

Proposed fix

Implement the function, delete it with its callers, or declare it abstract or as an interface member. Tune the thrown messages with markers.

export async function exportLedger(orgId: string): Promise<Blob> {
  return new Blob([`ledger,${orgId}\n`], { type: "text/csv" });
}

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.stub_shipped]
enabled = true
action = "warning"
markers = ["not implemented" # thrown-error messages that mark a stub, matched as whole words
Default enabled state
LanguageEnabled by default
PythonYes
JS / TSYes
RustYes

Weak Test Oracle (weak_test_oracle)

What it is

A test has no direct assertion, matcher, or expected exception — check whether it verifies behavior or only executes code.

Why it's bad

A test that only executes code can pass when behavior is wrong.

Example

Problem

def save(value: int) -> int:
    return value


def test_saves() -> None:
    saved = save(1)
    save(saved)

Proposed fix

Assert an observable result or expected exception.

def save(value: int) -> int:
    return value


def test_saves() -> None:
    assert save(1) == 1

Problem

import test from "node:test";

function save(value: number): number {
  return value;
}

test("saves value", () => {
  save(1);
});

Proposed fix

Assert the result, state, or expected rejection with a matcher.

import assert from "node:assert/strict";
import test from "node:test";

function save(value: number): number {
  return value;
}

test("saves value", () => {
  assert.equal(save(1), 1);
});

Tune It

Replace <lang> with python, javascript, typescript, or rust.

[smells.<lang>.rules.weak_test_oracle]
enabled = true
action = "warning"
assertion_functions = ["expect*", "assert*", "verify*"] # globs for imported helpers that count as checks
Default enabled state
LanguageEnabled by default
PythonNo
JS / TSNo
RustNo