Skip to content

A let binding that's never reassigned anywhere in the file should be const — it signals to a reader that the value doesn't change, and rules out an entire class of reassignment bugs at compile time.

ts
let total = price + tax; // flagged — never reassigned
return total;
ts
// fix
const total = price + tax;
return total;

Reassignment is tracked by name across the whole file, including inside nested closures — a closure that captures and reassigns an outer let still counts, so the outer binding is correctly left alone:

ts
let count = 0;
function increment() {
  count += 1; // reassignment, found even though it's in a nested function
}

Declaration sites are scoped to simple-identifier bindings only in this version — a destructured let declaration (let { a, b } = obj) is skipped. Reassignment detection is broader: a let-declared name is recognised as reassigned when it's the target of a plain assignment (x = v), an object/array destructuring-assignment (({ a, b } = v), ([a, b] = v), including renamed properties, rest elements, and defaults), or a for...of/for...in loop head (for (x of xs), for ([a, b] of pairs)).

ts
let token: string;
let slug: string;
({ token, slug } = await fetchPair()); // reassignment — not flagged

Tracking is name-based rather than true lexical scope: a shadowed let that shares a name with a reassigned variable in a different scope won't be flagged even if that specific binding is itself never reassigned. This is a deliberate false-negative, not a false-positive — the safer direction for a "should be const" suggestion.

Overlap with ESLint's prefer-const rule is expected and acceptable.

toml
# cofferdam.toml
[checks."Refactor.PreferConstOverLet"]
severity = "medium"   # raise if your project treats this as more than a style nit

MIT License