diff --git a/.changeset/tired-results-knock.md b/.changeset/tired-results-knock.md new file mode 100644 index 000000000000..c7b21121fcc8 --- /dev/null +++ b/.changeset/tired-results-knock.md @@ -0,0 +1,29 @@ +--- +"@biomejs/biome": minor +--- + +Implemented [#7174](https://github.com/biomejs/biome/issues/7174). [`useConst`](https://biomejs.dev/linter/rules/use-const/) no longer reports variables that are read before being written. + +Previously, `useConst` reported uninitialised variables that were read in an inner function before being written, as shown in the following example: + +```js +let v; +function f() { + return v; +} +v = 0; +``` + +This can produce false positives in the case where `f` is called before `v` has been written, as in the following code: + +```js +let v; +function f() { + return v; +} +console.log(f()); // print `undefined` +v = 0; +``` +Although this is an expected behavior of the original implementation, we consider it problematic since the rule’s fix is marked as safe. +To avoid false positives like this, the rule now ignores the previous examples. +However, this has the disadvantage of resulting in false negatives, such as not reporting the first example. diff --git a/crates/biome_js_analyze/src/lint/style/use_const.rs b/crates/biome_js_analyze/src/lint/style/use_const.rs index 74222059c369..6a94a9ec69b1 100644 --- a/crates/biome_js_analyze/src/lint/style/use_const.rs +++ b/crates/biome_js_analyze/src/lint/style/use_const.rs @@ -1,7 +1,4 @@ -use crate::{ - JsRuleAction, - services::{control_flow::AnyJsControlFlowRoot, semantic::Semantic}, -}; +use crate::{JsRuleAction, services::semantic::Semantic}; use biome_analyze::{ FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; @@ -74,6 +71,19 @@ declare_lint_rule! { /// a; // the variable is read before its assignment /// a = 0; /// ``` + /// + /// ## Caveats + /// + /// Since v2.2, the rule no longer reports variables that are read in an inner function before being written. + /// This can result in false negatives. For example, the following code is now valid: + /// + /// ```js + /// let a; + /// function f() { + /// return a; // read + /// } + /// a = 0; // written + /// ``` pub UseConst { version: "1.0.0", name: "useConst", @@ -219,7 +229,7 @@ fn check_binding_can_be_const( let binding_scope = binding.scope(model); let write = writes.next()?; - // If teher are multiple assignement or the write is not in the same scope + // If there are multiple assignment or the write is not in the same scope if writes.next().is_some() || write.scope() != binding_scope { return None; } @@ -231,23 +241,8 @@ fn check_binding_can_be_const( return None; } - let mut refs = binding.all_references(model); - // If a read precedes the write, don't report it. - // Ignore reads that are in an inner control flow root. - // For example, this ignores reads inside a function: - // ```js - // let v; - // function f() { v; } - // ``` - let next_ref = refs.find(|x| { - x.is_write() - || !x - .scope() - .ancestors() - .take_while(|scope| scope != &binding_scope) - .any(|scope| AnyJsControlFlowRoot::can_cast(scope.syntax().kind())) - }); - if matches!(next_ref, Some(next_ref) if next_ref.is_read()) { + // If a read occurs before a write. + if binding.all_references(model).next()?.is_read() { return None; } diff --git a/crates/biome_js_analyze/tests/specs/style/useConst/invalid.jsonc.snap b/crates/biome_js_analyze/tests/specs/style/useConst/invalid.jsonc.snap index 11c45122c48b..a8a2cb45d3ed 100644 --- a/crates/biome_js_analyze/tests/specs/style/useConst/invalid.jsonc.snap +++ b/crates/biome_js_analyze/tests/specs/style/useConst/invalid.jsonc.snap @@ -553,23 +553,6 @@ invalid.jsonc:1:1 lint/style/useConst ━━━━━━━━━━━━━━ let x; function foo() { bar(x); } x = 0; ``` -# Diagnostics -``` -invalid.jsonc:1:1 lint/style/useConst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This let declares a variable that is only assigned once. - - > 1 │ let x; function foo() { bar(x); } x = 0; - │ ^^^ - - i 'x' is only assigned here. - - > 1 │ let x; function foo() { bar(x); } x = 0; - │ ^ - - -``` - # Input ```cjs /*eslint use-x:error*/ let x = 1 @@ -1444,20 +1427,3 @@ invalid.jsonc:1:29 lint/style/useConst FIXABLE ━━━━━━━━━━ ```cjs let x; function foo() { bar(x); } x = 0; ``` - -# Diagnostics -``` -invalid.jsonc:1:1 lint/style/useConst ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This let declares a variable that is only assigned once. - - > 1 │ let x; function foo() { bar(x); } x = 0; - │ ^^^ - - i 'x' is only assigned here. - - > 1 │ let x; function foo() { bar(x); } x = 0; - │ ^ - - -```