Skip to content

Commit fb8e3e7

Browse files
fix(noInvalidUseBeforeDeclaration): handle class, enum, import-equals (#8113)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 041196b commit fb8e3e7

File tree

15 files changed

+265
-51
lines changed

15 files changed

+265
-51
lines changed

.changeset/twenty-signs-turn.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [`noInvalidUseBeforeDeclaration`](https://biomejs.dev/linter/rules/no-invalid-use-before-declaration/).
6+
The rule now reports invalid use of classes, enums, and TypeScript's import-equals before their declarations.
7+
8+
The following code is now reported as invalid:
9+
10+
```js
11+
new C();
12+
class C {}
13+
```

crates/biome_configuration/src/analyzer/linter/rules.rs

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

crates/biome_js_analyze/src/lint/correctness/no_invalid_use_before_declaration.rs

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ use biome_rowan::{AstNode, SyntaxNodeOptionExt, TextRange};
1111
use biome_rule_options::no_invalid_use_before_declaration::NoInvalidUseBeforeDeclarationOptions;
1212

1313
declare_lint_rule! {
14-
/// Disallow the use of variables and function parameters before their declaration
14+
/// Disallow the use of variables, function parameters, classes, and enums before their declaration
1515
///
16-
/// JavaScript doesn't allow the use of block-scoped variables (`let`, `const`) and function parameters before their declaration.
16+
/// JavaScript doesn't allow the use of block-scoped variables (`let`, `const`), function parameters, and classes before their declaration.
17+
/// Similarly TypeScript doesn't allow the use of enums before their declaration.
1718
/// A `ReferenceError` will be thrown with any attempt to access the variable or the parameter before its declaration.
1819
///
1920
/// The rule also reports the use of variables declared with `var` before their declarations.
@@ -40,14 +41,16 @@ declare_lint_rule! {
4041
/// function f(a = b, b = 0) {}
4142
/// ```
4243
///
44+
/// ```js,expect_diagnostic
45+
/// new C();
46+
/// class C {}
47+
/// ```
48+
///
4349
/// ### Valid
4450
///
4551
/// ```js
4652
/// f();
4753
/// function f() {}
48-
///
49-
/// new C();
50-
/// class C {}
5154
/// ```
5255
///
5356
/// ```js
@@ -60,6 +63,14 @@ declare_lint_rule! {
6063
/// function f() { return CONSTANT; }
6164
/// const CONSTANT = 0;
6265
/// ```
66+
///
67+
/// ```ts
68+
/// function f() {
69+
/// new C();
70+
/// }
71+
/// let c: C;
72+
/// class C {}
73+
/// ```
6374
pub NoInvalidUseBeforeDeclaration {
6475
version: "1.5.0",
6576
name: "noInvalidUseBeforeDeclaration",
@@ -105,20 +116,21 @@ impl Rule for NoInvalidUseBeforeDeclaration {
105116
let Ok(declaration_kind) = DeclarationKind::try_from(&declaration) else {
106117
continue;
107118
};
108-
let declaration_end = declaration.range().end();
109-
let declaration_control_flow_root =
110-
if let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = declaration
111-
.parent_binding_pattern_declaration()
112-
.unwrap_or(declaration)
113-
{
114-
declarator
115-
.syntax()
116-
.ancestors()
117-
.skip(1)
118-
.find(|ancestor| AnyJsControlFlowRoot::can_cast(ancestor.kind()))
119-
} else {
120-
None
121-
};
119+
let declaration_end = if matches!(
120+
declaration_kind,
121+
DeclarationKind::Class | DeclarationKind::Enum
122+
) {
123+
// A class can be instantiated by its properties.
124+
// Enum members can be qualified by the enum name.
125+
id.range().end()
126+
} else {
127+
declaration.range().end()
128+
};
129+
let declaration_control_flow_root = declaration
130+
.syntax()
131+
.ancestors()
132+
.skip(1)
133+
.find(|ancestor| AnyJsControlFlowRoot::can_cast(ancestor.kind()));
122134
for reference in binding.all_references() {
123135
if reference.range_start() < declaration_end {
124136
let reference_syntax = reference.syntax();
@@ -177,6 +189,8 @@ impl Rule for NoInvalidUseBeforeDeclaration {
177189
binding_range: declaration_range,
178190
} = state;
179191
let declaration_kind_text = match declaration_kind {
192+
DeclarationKind::Class => "class",
193+
DeclarationKind::Enum => "enum",
180194
DeclarationKind::EnumMember => "enum member",
181195
DeclarationKind::Parameter => "parameter",
182196
DeclarationKind::Variable => "variable",
@@ -202,8 +216,10 @@ pub struct InvalidUseBeforeDeclaration {
202216
binding_range: TextRange,
203217
}
204218

205-
#[derive(Debug, Copy, Clone)]
219+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
206220
pub enum DeclarationKind {
221+
Class,
222+
Enum,
207223
EnumMember,
208224
Parameter,
209225
Variable,
@@ -220,9 +236,8 @@ impl TryFrom<&AnyJsBindingDeclaration> for DeclarationKind {
220236
| AnyJsBindingDeclaration::JsArrayBindingPatternRestElement(_)
221237
| AnyJsBindingDeclaration::JsObjectBindingPatternProperty(_)
222238
| AnyJsBindingDeclaration::JsObjectBindingPatternRest(_)
223-
| AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_) => {
224-
Ok(Self::Variable)
225-
}
239+
| AnyJsBindingDeclaration::JsObjectBindingPatternShorthandProperty(_)
240+
| AnyJsBindingDeclaration::TsImportEqualsDeclaration(_) => Ok(Self::Variable),
226241
AnyJsBindingDeclaration::JsVariableDeclarator(declarator) => {
227242
if let Some(var_decl) = declarator.declaration()
228243
&& let Some(var_decl_clause) = var_decl.parent::<JsVariableDeclarationClause>()
@@ -239,6 +254,21 @@ impl TryFrom<&AnyJsBindingDeclaration> for DeclarationKind {
239254
AnyJsBindingDeclaration::JsFormalParameter(_)
240255
| AnyJsBindingDeclaration::JsRestParameter(_)
241256
| AnyJsBindingDeclaration::TsPropertyParameter(_) => Ok(Self::Parameter),
257+
AnyJsBindingDeclaration::JsClassDeclaration(_)
258+
| AnyJsBindingDeclaration::JsClassExportDefaultDeclaration(_) => {
259+
if value.parent::<TsDeclareStatement>().is_some() {
260+
Err(())
261+
} else {
262+
Ok(Self::Class)
263+
}
264+
}
265+
AnyJsBindingDeclaration::TsEnumDeclaration(_) => {
266+
if value.parent::<TsDeclareStatement>().is_some() {
267+
Err(())
268+
} else {
269+
Ok(Self::Enum)
270+
}
271+
}
242272
// Other declarations allow use before definition
243273
AnyJsBindingDeclaration::JsArrowFunctionExpression(_)
244274
| AnyJsBindingDeclaration::JsBogusParameter(_)
@@ -249,20 +279,16 @@ impl TryFrom<&AnyJsBindingDeclaration> for DeclarationKind {
249279
| AnyJsBindingDeclaration::JsFunctionDeclaration(_)
250280
| AnyJsBindingDeclaration::JsFunctionExpression(_)
251281
| AnyJsBindingDeclaration::TsDeclareFunctionDeclaration(_)
252-
| AnyJsBindingDeclaration::JsClassDeclaration(_)
253282
| AnyJsBindingDeclaration::JsClassExpression(_)
254283
| AnyJsBindingDeclaration::TsInterfaceDeclaration(_)
255284
| AnyJsBindingDeclaration::TsTypeAliasDeclaration(_)
256-
| AnyJsBindingDeclaration::TsEnumDeclaration(_)
257285
| AnyJsBindingDeclaration::TsExternalModuleDeclaration(_)
258286
| AnyJsBindingDeclaration::TsModuleDeclaration(_)
259287
| AnyJsBindingDeclaration::JsShorthandNamedImportSpecifier(_)
260288
| AnyJsBindingDeclaration::JsNamedImportSpecifier(_)
261289
| AnyJsBindingDeclaration::JsBogusNamedImportSpecifier(_)
262290
| AnyJsBindingDeclaration::JsDefaultImportSpecifier(_)
263291
| AnyJsBindingDeclaration::JsNamespaceImportSpecifier(_)
264-
| AnyJsBindingDeclaration::TsImportEqualsDeclaration(_)
265-
| AnyJsBindingDeclaration::JsClassExportDefaultDeclaration(_)
266292
| AnyJsBindingDeclaration::JsFunctionExportDefaultDeclaration(_)
267293
| AnyJsBindingDeclaration::TsDeclareFunctionExportDefaultDeclaration(_)
268294
| AnyJsBindingDeclaration::JsCatchDeclaration(_) => Err(()),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
function f() {
3+
const enum1Member = E1.A;
4+
}
5+
enum E1 { A }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: enum.ts
4+
---
5+
# Input
6+
```ts
7+
8+
function f() {
9+
const enum1Member = E1.A;
10+
}
11+
enum E1 { A }
12+
13+
```

crates/biome_js_analyze/tests/specs/correctness/noInvalidUseBeforeDeclaration/invalid.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ var f = f;
1616
function f(a = b, b = 0) {}
1717

1818
function g(a = a) {}
19+
20+
const instance = new Class1();
21+
class Class1 {}
22+
23+
const instance2 = new Class2();
24+
export default class Class2 {}

crates/biome_js_analyze/tests/specs/correctness/noInvalidUseBeforeDeclaration/invalid.js.snap

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/biome_js_analyze/tests/spec_tests.rs
33
expression: invalid.js
4-
snapshot_kind: text
54
---
65
# Input
76
```js
@@ -24,6 +23,12 @@ function f(a = b, b = 0) {}
2423
2524
function g(a = a) {}
2625
26+
const instance = new Class1();
27+
class Class1 {}
28+
29+
const instance2 = new Class2();
30+
export default class Class2 {}
31+
2732
```
2833

2934
# Diagnostics
@@ -176,6 +181,7 @@ invalid.js:18:16 lint/correctness/noInvalidUseBeforeDeclaration ━━━━━
176181
> 18 │ function g(a = a) {}
177182
│ ^
178183
19 │
184+
20 │ const instance = new Class1();
179185
180186
i The parameter is declared here:
181187
@@ -184,6 +190,52 @@ invalid.js:18:16 lint/correctness/noInvalidUseBeforeDeclaration ━━━━━
184190
> 18 │ function g(a = a) {}
185191
│ ^
186192
19 │
193+
20 │ const instance = new Class1();
194+
195+
196+
```
197+
198+
```
199+
invalid.js:20:22 lint/correctness/noInvalidUseBeforeDeclaration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
200+
201+
× This class is used before its declaration.
202+
203+
18 │ function g(a = a) {}
204+
19 │
205+
> 20 │ const instance = new Class1();
206+
│ ^^^^^^
207+
21 │ class Class1 {}
208+
22 │
209+
210+
i The class is declared here:
211+
212+
20 │ const instance = new Class1();
213+
> 21 │ class Class1 {}
214+
│ ^^^^^^
215+
22 │
216+
23 │ const instance2 = new Class2();
217+
218+
219+
```
220+
221+
```
222+
invalid.js:23:23 lint/correctness/noInvalidUseBeforeDeclaration ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223+
224+
× This class is used before its declaration.
225+
226+
21 │ class Class1 {}
227+
22 │
228+
> 23 │ const instance2 = new Class2();
229+
│ ^^^^^^
230+
24 │ export default class Class2 {}
231+
25 │
232+
233+
i The class is declared here:
234+
235+
23 │ const instance2 = new Class2();
236+
> 24 │ export default class Class2 {}
237+
│ ^^^^^^
238+
25 │
187239
188240
189241
```

crates/biome_js_analyze/tests/specs/correctness/noInvalidUseBeforeDeclaration/invalid.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ class C {
22
constructor(readonly a = b, readonly b = 0) {}
33
}
44

5+
const member = E.A;
56
enum E {
67
A = B,
78
B,
89
}
10+
11+
namespace Ns {
12+
const c = new Class();
13+
}
14+
class Class {}
15+
16+
x;
17+
import x = require("file");

0 commit comments

Comments
 (0)