eslint-plugin-auto-let
v0.1.0
Published
Automatically convert const to let when mutation is proven via AST scope analysis
Maintainers
Readme
eslint-plugin-auto-let
ESLint plugin that rewrites const to let when a const is reassigned within the same function or module.
Pairs well with prefer-const and IDE lint-on-save:
prefer-const may rewrite let to const before you've finished writing reassignment logic; this rule undoes that when a write is present.
Motivation & Warning
- You still want to use
prefer-constwhere it matters - You are inconvenienced by
letbeing converted toconstwhen saving your file immediately after the let declaration - You are aware of the scenarios where this can go wrong (accidentally writing to a const in the same function)
Behavior
The single rule auto-let/convert-const-to-let converts simple const declarations to let only when:
- The declaration is a single identifier (
const x = 1, not destructuring or multi-declaration) - At least one write reference exists (
x = ...,x += 1,x++, etc.) - Ignores
UPPERCASE_SNAKE_CASEconstants - Only happens in the same function (ignores assignments that happen inside other functions (including nested functions))
Example
Before saving:
function someFuction() {
let count: number = 0; // this will be converted to const by prefer-const
}After saving:
function someFuction() {
const count: number = 0; // converted to const
}Adding your assignment
function someFuction() {
const count: number = 0;
count++;
console.log(count);
// did not save yet, but after you save, the const count declaration will be converted to let
}After saving
function someFuction() {
let count: number = 0; // correctly converted const to let
count++;
console.log(count);
}Installation
npm install -D eslint-plugin-auto-let @typescript-eslint/parserRequirements
- Node.js >= 18.18
- ESLint >= 8.57
@typescript-eslint/parser^8 (TypeScript only)
Eslint config
// eslint.config.js
import eslint from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import autoLet from "eslint-plugin-auto-let";
export default [
eslint.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parser: tsParser,
},
plugins: {
"auto-let": autoLet,
},
rules: {
"auto-let/convert-const-to-let": "error",
},
},
];Enable fix-on-save in your editor (source.fixAll.eslint) so the rule runs alongside prefer-const while you write.
Some examples (const → let)
Simple reassignment:
// before
const count = 0;
count++;
// after (eslint --fix)
let count = 0;
count++;Assignment operators:
// before
const total = 10;
total += 5;
// after
let total = 10;
total += 5;Assign in branches, then use later (common with prefer-const on save):
// before
function formatLabel(input?: string) {
const label: string;
if (input) {
label = input;
} else {
label = "default";
}
label = label.toUpperCase();
}
// after
function formatLabel(input?: string) {
let label: string;
if (input) {
label = input;
} else {
label = "default";
}
label = label.toUpperCase();
}Reassignment inside a loop:
// before
const cursor = 0;
while (cursor < items.length) {
cursor = next(cursor);
}
// after
let cursor = 0;
while (cursor < items.length) {
cursor = next(cursor);
}Unchanged (left as-is)
No reassignment — binding never changes:
const name = "alice";
console.log(name);Destructuring or multiple declarations — not simple const x = …:
const { x, y } = point;
const a = 1,
b = 2;Property mutation — the binding is unchanged, only the object contents:
const items: string[] = [];
items.push("a");UPPERCASE_SNAKE_CASE — treated as a true constant:
const MAX_RETRIES = 3;
MAX_RETRIES = 5; // rule ignores this binding, prefer-const will keep this as a constantImport bindings:
import fs from "node:fs";
fs = null; // rule ignores importsReassignment from a nested function — different scope, not auto-fixed:
function outer() {
const value = 1;
function inner() {
value = 2; // this is ignored
}
}Already let:
let count = 0;
count++;Alternatives
- Always use
let: eslint-plugin-prefer-let
License
MIT
