Skip to content

Latest commit

Β 

History

History
86 lines (64 loc) Β· 2.72 KB

File metadata and controls

86 lines (64 loc) Β· 2.72 KB

no-declarations-before-early-exit

πŸ“ Disallow declarations before conditional early exits when they are only used after the exit.

πŸ’Ό This rule is enabled in the following configs: βœ… recommended, β˜‘οΈ unopinionated.

πŸ”§ This rule is automatically fixable by the --fix CLI option.

Declare variables as close as possible to where they are used. If a variable is declared before a guard clause but only used after it, the declaration can be moved below the guard. This avoids unnecessary initialization on the early-exit path and keeps the variable scope tighter.

This rule reports declarations before return, throw, break, and continue guard clauses.

Examples

// ❌
function foo(bar) {
	const result = 1;
	if (!bar) {
		return;
	}

	console.log(result);
}
// βœ…
function foo(bar) {
	if (!bar) {
		return;
	}

	const result = 1;
	console.log(result);
}
// ❌
function foo(bar) {
	const result = 1;
	if (!bar) {
		throw new Error();
	}

	console.log(result);
}
// βœ…
function foo(bar) {
	if (!bar) {
		throw new Error();
	}

	const result = 1;
	console.log(result);
}
// ❌
function foo(bar) {
	const result = getResult();
	if (!bar) {
		return;
	}

	console.log(result);
}

The last example is reported without an autofix because moving a function call can change observable behavior.

Limitations

This rule intentionally uses a simple statement-list scan instead of full data-flow analysis. It only checks declarations and guard clauses that are direct children of the same block or top-level program. Switch cases are only checked when the case body is wrapped in a block. Non-adjacent declarations with nontrivial initializers are ignored because moving them across intervening statements can change values or timing. Declarations are also ignored when the initializer and the guard share a variable that either side mutates, since reordering them would change behavior (for example, const x = array.pop() before if (array.length > 0)).

Declarations whose initializer contains await or yield are ignored: moving them below the guard would evaluate the guard condition before the suspension instead of after it, which can change observable timing.

Declarations initialized by a React hook call (for example, const value = useMemo(…)) are ignored, because hooks must run unconditionally and moving one below an early exit would violate the Rules of Hooks.