diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs index e269e47540ed..25565ca96e0d 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs @@ -10,7 +10,8 @@ use biome_js_syntax::{ use biome_rowan::{chain_trivia_pieces, AstNode, BatchMutationExt}; declare_rule! { - /// Disallow `new` operators with global non-constructor functions. + /// Disallow `new` operators with global non-constructor functions and + /// non-constructor built-in objects. /// /// Some global functions cannot be called using the new operator and /// will throw a `TypeError` if you attempt to do so. These functions are: @@ -18,6 +19,15 @@ declare_rule! { /// - [`Symbol`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol) /// - [`BigInt`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt) /// + /// Several built-in objects cannot be instantiated and will throw a `TypeError` + /// if you try to execute them as constructors. These objects are: + /// + /// - [`Math`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math) + /// - [`JSON`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) + /// - [`Reflect`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Reflect) + /// - [`Atomics`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Atomics) + /// - [`Intl`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl) + /// /// Source: https://eslint.org/docs/latest/rules/no-new-native-nonconstructor/ /// /// ## Examples @@ -25,21 +35,39 @@ declare_rule! { /// ### Invalid /// /// ```js,expect_diagnostic - /// var foo = new Symbol('foo'); - /// var bar = new BigInt(9007199254740991); + /// var symbol = new Symbol('foo'); + /// var bigInt = new BigInt(9007199254740991); + /// var math = new Math(); + /// var json = new JSON(); + /// var reflect = new Reflect(); + /// var atomics = new Atomics(); + /// var intl = new Intl(); /// ``` /// /// ## Valid /// /// ```js - /// var foo = Symbol('foo'); - /// var bar = BigInt(9007199254740991); + /// var symbol = Symbol('foo'); + /// var bigInt = BigInt(9007199254740991); /// - /// function baz(Symbol) { - /// const qux = new Symbol("baz"); + /// // Ignores shadowed + /// function foo(Symbol) { + /// const symbol = new Symbol('foo'); + /// } + /// function bar(BigInt) { + /// const bigInt = new BigInt(9007199254740991); + /// } + /// function baz(Math) { + /// const math = new Math(); /// } - /// function quux(BigInt) { - /// const corge = new BigInt(9007199254740991); + /// function qux(JSON) { + /// const json = new JSON(); + /// } + /// function quux(Reflect) { + /// const reflect = new Reflect(); + /// } + /// function corge(Intl) { + /// const intl = new Intl(); /// } /// ``` pub(crate) NoInvalidNewBuiltin { @@ -59,25 +87,24 @@ impl Rule for NoInvalidNewBuiltin { let callee = ctx.query().callee().ok()?; let (reference, name) = global_identifier(&callee)?; match name.text() { - "Symbol" | "BigInt" => ctx.model().binding(&reference).is_none().then_some(name), + "Symbol" | "BigInt" | "Math" | "JSON" | "Reflect" | "Atomics" | "Intl" => { + ctx.model().binding(&reference).is_none().then_some(name) + } _ => None, } } - fn diagnostic( - ctx: &RuleContext, - builtin_fn_name: &Self::State, - ) -> Option { + fn diagnostic(ctx: &RuleContext, builtin_name: &Self::State) -> Option { Some(RuleDiagnostic::new( rule_category!(), ctx.query().range(), markup! { - "`"{builtin_fn_name.text()}"`"" cannot be called as a constructor." + "`"{builtin_name.text()}"`"" cannot be called as a constructor." }, )) } - fn action(ctx: &RuleContext, _: &Self::State) -> Option { + fn action(ctx: &RuleContext, builtin_name: &Self::State) -> Option { let node = ctx.query(); let call_expression = convert_new_expression_to_call_expression(node)?; let mut mutation = ctx.root().begin(); @@ -85,12 +112,15 @@ impl Rule for NoInvalidNewBuiltin { node.clone().into(), call_expression.into(), ); - Some(JsRuleAction { - category: ActionCategory::QuickFix, - applicability: Applicability::MaybeIncorrect, - message: markup! { "Remove ""`new`""." }.to_owned(), - mutation, - }) + match builtin_name.text() { + "Symbol" | "BigInt" => Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { "Remove ""`new`""." }.to_owned(), + mutation, + }), + _ => None, + } } } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js index c4bbcfd81b91..4b9e58c405f5 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js @@ -9,3 +9,33 @@ function bar() { return function BigInt() {}; } var baz = new BigInt(9007199254740991); + +var foo = new Math(); +function bar() { + return function Math() {}; +} +var baz = new Math(); + +var foo = new JSON(); +function bar() { + return function JSON() {}; +} +var baz = new JSON(); + +var foo = new Reflect(); +function bar() { + return function Reflect() {}; +} +var baz = new Reflect(); + +var foo = new Atomics(); +function bar() { + return function Atomics() {}; +} +var baz = new Atomics(); + +var foo = new Intl(); +function bar() { + return function Intl() {}; +} +var baz = new Intl(); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap index 675d68292289..c7077f8ef492 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap @@ -16,6 +16,36 @@ function bar() { } var baz = new BigInt(9007199254740991); +var foo = new Math(); +function bar() { + return function Math() {}; +} +var baz = new Math(); + +var foo = new JSON(); +function bar() { + return function JSON() {}; +} +var baz = new JSON(); + +var foo = new Reflect(); +function bar() { + return function Reflect() {}; +} +var baz = new Reflect(); + +var foo = new Atomics(); +function bar() { + return function Atomics() {}; +} +var baz = new Atomics(); + +var foo = new Intl(); +function bar() { + return function Intl() {}; +} +var baz = new Intl(); + ``` # Diagnostics @@ -84,6 +114,7 @@ invalid.js:11:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━ > 11 │ var baz = new BigInt(9007199254740991); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 12 │ + 13 │ var foo = new Math(); i Suggested fix: Remove `new`. @@ -92,4 +123,153 @@ invalid.js:11:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━ ``` +``` +invalid.js:13:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Math` cannot be called as a constructor. + + 11 │ var baz = new BigInt(9007199254740991); + 12 │ + > 13 │ var foo = new Math(); + │ ^^^^^^^^^^ + 14 │ function bar() { + 15 │ return function Math() {}; + + +``` + +``` +invalid.js:17:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Math` cannot be called as a constructor. + + 15 │ return function Math() {}; + 16 │ } + > 17 │ var baz = new Math(); + │ ^^^^^^^^^^ + 18 │ + 19 │ var foo = new JSON(); + + +``` + +``` +invalid.js:19:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `JSON` cannot be called as a constructor. + + 17 │ var baz = new Math(); + 18 │ + > 19 │ var foo = new JSON(); + │ ^^^^^^^^^^ + 20 │ function bar() { + 21 │ return function JSON() {}; + + +``` + +``` +invalid.js:23:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `JSON` cannot be called as a constructor. + + 21 │ return function JSON() {}; + 22 │ } + > 23 │ var baz = new JSON(); + │ ^^^^^^^^^^ + 24 │ + 25 │ var foo = new Reflect(); + + +``` + +``` +invalid.js:25:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Reflect` cannot be called as a constructor. + + 23 │ var baz = new JSON(); + 24 │ + > 25 │ var foo = new Reflect(); + │ ^^^^^^^^^^^^^ + 26 │ function bar() { + 27 │ return function Reflect() {}; + + +``` + +``` +invalid.js:29:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Reflect` cannot be called as a constructor. + + 27 │ return function Reflect() {}; + 28 │ } + > 29 │ var baz = new Reflect(); + │ ^^^^^^^^^^^^^ + 30 │ + 31 │ var foo = new Atomics(); + + +``` + +``` +invalid.js:31:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Atomics` cannot be called as a constructor. + + 29 │ var baz = new Reflect(); + 30 │ + > 31 │ var foo = new Atomics(); + │ ^^^^^^^^^^^^^ + 32 │ function bar() { + 33 │ return function Atomics() {}; + + +``` + +``` +invalid.js:35:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Atomics` cannot be called as a constructor. + + 33 │ return function Atomics() {}; + 34 │ } + > 35 │ var baz = new Atomics(); + │ ^^^^^^^^^^^^^ + 36 │ + 37 │ var foo = new Intl(); + + +``` + +``` +invalid.js:37:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Intl` cannot be called as a constructor. + + 35 │ var baz = new Atomics(); + 36 │ + > 37 │ var foo = new Intl(); + │ ^^^^^^^^^^ + 38 │ function bar() { + 39 │ return function Intl() {}; + + +``` + +``` +invalid.js:41:11 lint/nursery/noInvalidNewBuiltin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! `Intl` cannot be called as a constructor. + + 39 │ return function Intl() {}; + 40 │ } + > 41 │ var baz = new Intl(); + │ ^^^^^^^^^^ + 42 │ + + +``` + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js index 7e922006c946..200879849e61 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js @@ -15,3 +15,43 @@ function BigInt() {} new BigInt(); new foo(BigInt); new foo(bar, BigInt); + +function foo(Math) { + var math = new Math(); +} +function Math() {} +new Math(); +new foo(Math); +new foo(bar, Math); + +function foo(JSON) { + var json = new JSON(); +} +function JSON() {} +new JSON(); +new foo(JSON); +new foo(bar, JSON); + +function foo(Reflect) { + var reflect = new Reflect(); +} +function Reflect() {} +new Reflect(); +new foo(Reflect); +new foo(bar, Reflect); + +function foo(Atomics) { + var atomics = new Atomics(); +} +function Atomics() {} +new Atomics(); +new foo(Atomics); +new foo(bar, Atomics); + +function foo(Intl) { + var atomics = new Intl(); +} +function Intl() {} +new Intl(); +new foo(Intl); +new foo(bar, Intl); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap index ffa291bc6a0e..41b32812840a 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap @@ -22,6 +22,46 @@ new BigInt(); new foo(BigInt); new foo(bar, BigInt); +function foo(Math) { + var math = new Math(); +} +function Math() {} +new Math(); +new foo(Math); +new foo(bar, Math); + +function foo(JSON) { + var json = new JSON(); +} +function JSON() {} +new JSON(); +new foo(JSON); +new foo(bar, JSON); + +function foo(Reflect) { + var reflect = new Reflect(); +} +function Reflect() {} +new Reflect(); +new foo(Reflect); +new foo(bar, Reflect); + +function foo(Atomics) { + var atomics = new Atomics(); +} +function Atomics() {} +new Atomics(); +new foo(Atomics); +new foo(bar, Atomics); + +function foo(Intl) { + var atomics = new Intl(); +} +function Intl() {} +new Intl(); +new foo(Intl); +new foo(bar, Intl); + ``` diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 375cd6abd0b4..f03285056256 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2201,7 +2201,7 @@ pub struct Nursery { #[bpaf(long("no-global-is-nan"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub no_global_is_nan: Option, - #[doc = "Disallow new operators with global non-constructor functions."] + #[doc = "Disallow new operators with global non-constructor functions and non-constructor built-in objects."] #[bpaf( long("no-invalid-new-builtin"), argument("on|off|warn"), diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 39476835b131..3f62be9bd325 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1013,7 +1013,7 @@ ] }, "noInvalidNewBuiltin": { - "description": "Disallow new operators with global non-constructor functions.", + "description": "Disallow new operators with global non-constructor functions and non-constructor built-in objects.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 5199c7a6caf4..7eb5f81f47fa 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -678,7 +678,7 @@ export interface Nursery { */ noGlobalIsNan?: RuleConfiguration; /** - * Disallow new operators with global non-constructor functions. + * Disallow new operators with global non-constructor functions and non-constructor built-in objects. */ noInvalidNewBuiltin?: RuleConfiguration; /** diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 39476835b131..3f62be9bd325 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1013,7 +1013,7 @@ ] }, "noInvalidNewBuiltin": { - "description": "Disallow new operators with global non-constructor functions.", + "description": "Disallow new operators with global non-constructor functions and non-constructor built-in objects.", "anyOf": [ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 6ef8b3269c55..c5d1cd31b20c 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -353,7 +353,8 @@ Use Number.isFinite instead of global isFinite. ### [noGlobalIsNan](/linter/rules/no-global-is-nan) Use Number.isNaN instead of global isNaN. ### [noInvalidNewBuiltin](/linter/rules/no-invalid-new-builtin) -Disallow new operators with global non-constructor functions. +Disallow new operators with global non-constructor functions and +non-constructor built-in objects. ### [noMisleadingInstantiator](/linter/rules/no-misleading-instantiator) Enforce proper usage of new and constructor. ### [noUselessElse](/linter/rules/no-useless-else) diff --git a/website/src/content/docs/linter/rules/no-invalid-new-builtin.md b/website/src/content/docs/linter/rules/no-invalid-new-builtin.md index 0eaae8c7e5a8..4ae2b2e9a3cd 100644 --- a/website/src/content/docs/linter/rules/no-invalid-new-builtin.md +++ b/website/src/content/docs/linter/rules/no-invalid-new-builtin.md @@ -8,7 +8,8 @@ title: noInvalidNewBuiltin (since vnext) This rule is part of the [nursery](/linter/rules/#nursery) group. ::: -Disallow `new` operators with global non-constructor functions. +Disallow `new` operators with global non-constructor functions and +non-constructor built-in objects. Some global functions cannot be called using the new operator and will throw a `TypeError` if you attempt to do so. These functions are: @@ -16,6 +17,15 @@ will throw a `TypeError` if you attempt to do so. These functions are: - [`Symbol`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol) - [`BigInt`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt) +Several built-in objects cannot be instantiated and will throw a `TypeError` +if you try to execute them as constructors. These objects are: + +- [`Math`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math) +- [`JSON`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/JSON) +- [`Reflect`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Reflect) +- [`Atomics`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Atomics) +- [`Intl`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl) + Source: https://eslint.org/docs/latest/rules/no-new-native-nonconstructor/ ## Examples @@ -23,49 +33,68 @@ Source: https://eslint.org/docs/latest/rules/no-new-native-nonconstructor/ ### Invalid ```jsx -var foo = new Symbol('foo'); -var bar = new BigInt(9007199254740991); +var symbol = new Symbol('foo'); +var bigInt = new BigInt(9007199254740991); +var math = new Math(); +var json = new JSON(); +var reflect = new Reflect(); +var atomics = new Atomics(); +var intl = new Intl(); ``` -
nursery/noInvalidNewBuiltin.js:1:11 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
+
nursery/noInvalidNewBuiltin.js:1:14 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
 
    `Symbol` cannot be called as a constructor.
   
-  > 1 │ var foo = new Symbol('foo');
-             ^^^^^^^^^^^^^^^^^
-    2 │ var bar = new BigInt(9007199254740991);
-    3 │ 
+  > 1 │ var symbol = new Symbol('foo');
+                ^^^^^^^^^^^^^^^^^
+    2 │ var bigInt = new BigInt(9007199254740991);
+    3 │ var math = new Math();
   
    Suggested fix: Remove `new`.
   
-    1 │ var·foo·=·new·Symbol('foo');
-            ----              
-nursery/noInvalidNewBuiltin.js:2:11 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
+    1 │ var·symbol·=·new·Symbol('foo');
+               ----              
+nursery/noInvalidNewBuiltin.js:2:14 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
 
    `BigInt` cannot be called as a constructor.
   
-    1 │ var foo = new Symbol('foo');
-  > 2 │ var bar = new BigInt(9007199254740991);
-             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-    3 │ 
+    1 │ var symbol = new Symbol('foo');
+  > 2 │ var bigInt = new BigInt(9007199254740991);
+                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    3 │ var math = new Math();
+    4 │ var json = new JSON();
   
    Suggested fix: Remove `new`.
   
-    2 │ var·bar·=·new·BigInt(9007199254740991);
-            ----                         
+    2 │ var·bigInt·=·new·BigInt(9007199254740991);
+               ----                         
 
## Valid ```jsx -var foo = Symbol('foo'); -var bar = BigInt(9007199254740991); +var symbol = Symbol('foo'); +var bigInt = BigInt(9007199254740991); -function baz(Symbol) { - const qux = new Symbol("baz"); +// Ignores shadowed +function foo(Symbol) { + const symbol = new Symbol('foo'); +} +function bar(BigInt) { + const bigInt = new BigInt(9007199254740991); +} +function baz(Math) { + const math = new Math(); +} +function qux(JSON) { + const json = new JSON(); +} +function quux(Reflect) { + const reflect = new Reflect(); } -function quux(BigInt) { - const corge = new BigInt(9007199254740991); +function corge(Intl) { + const intl = new Intl(); } ```