Skip to content

Commit cb4fbb9

Browse files
todor-acamc314
andauthored
feat(linter/eslint): implement no-unreachable-loop rule (#23975)
## Summary - add native `eslint/no-unreachable-loop` support - support the ESLint `ignore` option for loop statement types - reuse `no-unreachable`'s effective CFG reachability helper (`effective_unreachable_blocks`) - generate oxlint config schema/types and rule metadata ## Why `no-unreachable-loop` is currently missing from Oxlint's ESLint rule coverage. The rule catches loops whose body cannot reach a second iteration, while preserving ESLint-compatible behavior for unreachable loops and configured ignored loop types. ## Performance The effective-reachability helper is a whole-CFG traversal. It is only needed to recognize a loop that is dead code *after* an infinite loop; the next-iteration search prunes infinite loops on its own. So the analysis runs on the CFG's base reachability first and builds the corrected map only when a loop would otherwise be reported *and* is a static infinite loop — a rare path the common case never hits. This keeps the rule self-contained and brings the `linter[binder.ts]` and `linter[kitchen-sink.tsx]` benches from a ~4-5% regression down to noise. ## AI Assistance This PR was implemented with AI assistance. --------- Co-authored-by: Cameron <cameron.clark@hey.com>
1 parent cd8fdfe commit cb4fbb9

9 files changed

Lines changed: 7059 additions & 131 deletions

File tree

apps/oxlint/src-js/package/config.generated.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export type NoReturnAssignMode = "always" | "except-parens";
156156
* Controls how hoisting is handled when checking for shadowing.
157157
*/
158158
export type HoistOption = "all" | "functions" | "functions-and-types" | "never" | "types";
159+
export type LoopType = "WhileStatement" | "DoWhileStatement" | "ForStatement" | "ForInStatement" | "ForOfStatement";
159160
export type NoUnusedVarsConfig = VarsOption | NoUnusedVarsOptions;
160161
export type VarsOption = "all" | "local";
161162
export type ArgsOption = "after-used" | "all" | "none";
@@ -1208,6 +1209,7 @@ export interface DummyRuleMap {
12081209
"no-unmodified-loop-condition"?: RuleNoConfig;
12091210
"no-unneeded-ternary"?: RuleNoConfig | [AllowWarnDeny, NoUnneededTernary];
12101211
"no-unreachable"?: RuleNoConfig;
1212+
"no-unreachable-loop"?: RuleNoConfig | [AllowWarnDeny, NoUnreachableLoopConfig];
12111213
"no-unsafe-finally"?: RuleNoConfig;
12121214
"no-unsafe-negation"?: RuleNoConfig | [AllowWarnDeny, NoUnsafeNegation];
12131215
"no-unsafe-optional-chaining"?: RuleNoConfig | [AllowWarnDeny, NoUnsafeOptionalChaining];
@@ -3592,6 +3594,9 @@ export interface NoUnneededTernary {
35923594
*/
35933595
defaultAssignment?: boolean;
35943596
}
3597+
export interface NoUnreachableLoopConfig {
3598+
ignore?: LoopType[];
3599+
}
35953600
export interface NoUnsafeNegation {
35963601
/**
35973602
* The `enforceForOrderingRelations` option determines whether negation is allowed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/generated/rules_enum.rs

Lines changed: 27 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_linter/src/rules.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ pub(crate) mod eslint {
175175
pub mod no_unmodified_loop_condition;
176176
pub mod no_unneeded_ternary;
177177
pub mod no_unreachable;
178+
pub mod no_unreachable_loop;
178179
pub mod no_unsafe_finally;
179180
pub mod no_unsafe_negation;
180181
pub mod no_unsafe_optional_chaining;

0 commit comments

Comments
 (0)