From c8ff3d94af23a6ef72bea9b5709c1bea89e18a7c Mon Sep 17 00:00:00 2001 From: Lucas Weng <30640930+lucasweng@users.noreply.github.com> Date: Sat, 23 Sep 2023 19:12:00 +0800 Subject: [PATCH] feat(lint/noInvalidNewBuiltin): add rule (#375) --- .../src/categories.rs | 1 + .../src/semantic_analyzers/nursery.rs | 2 + .../nursery/no_invalid_new_builtin.rs | 107 ++++++++++++++++++ .../nursery/noInvalidNewBuiltin/invalid.js | 11 ++ .../noInvalidNewBuiltin/invalid.js.snap | 103 +++++++++++++++++ .../nursery/noInvalidNewBuiltin/valid.js | 17 +++ .../nursery/noInvalidNewBuiltin/valid.js.snap | 27 +++++ .../src/configuration/linter/rules.rs | 82 +++++++++----- .../src/configuration/parse/json/rules.rs | 24 ++++ editors/vscode/configuration_schema.json | 7 ++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 ++ .../components/generated/NumberOfRules.astro | 2 +- .../src/content/docs/linter/rules/index.mdx | 2 + .../linter/rules/no-invalid-new-builtin.md | 79 +++++++++++++ 15 files changed, 446 insertions(+), 30 deletions(-) create mode 100644 crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap create mode 100644 website/src/content/docs/linter/rules/no-invalid-new-builtin.md diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 62a255622556..a82756fed962 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -95,6 +95,7 @@ define_categories! { "lint/nursery/noFallthroughSwitchClause": "https://biomejs.dev/linter/rules/no-fallthrough-switch-clause", "lint/nursery/noGlobalIsFinite": "https://biomejs.dev/linter/rules/no-global-is-finite", "lint/nursery/noGlobalIsNan": "https://biomejs.dev/linter/rules/no-global-is-nan", + "lint/nursery/noInvalidNewBuiltin": "https://biomejs.dev/lint/rules/no-invalid-new-builtin", "lint/nursery/noMisleadingInstantiator": "https://biomejs.dev/linter/rules/no-misleading-instantiator", "lint/nursery/noUselessElse": "https://biomejs.dev/lint/rules/no-useless-else", "lint/nursery/noVoid": "https://biomejs.dev/linter/rules/no-void", diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs index 7abceb997c1c..3c3d03e29640 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs @@ -5,6 +5,7 @@ use biome_analyze::declare_group; pub(crate) mod no_accumulating_spread; pub(crate) mod no_global_is_finite; pub(crate) mod no_global_is_nan; +pub(crate) mod no_invalid_new_builtin; pub(crate) mod use_exhaustive_dependencies; pub(crate) mod use_hook_at_top_level; pub(crate) mod use_is_array; @@ -16,6 +17,7 @@ declare_group! { self :: no_accumulating_spread :: NoAccumulatingSpread , self :: no_global_is_finite :: NoGlobalIsFinite , self :: no_global_is_nan :: NoGlobalIsNan , + self :: no_invalid_new_builtin :: NoInvalidNewBuiltin , self :: use_exhaustive_dependencies :: UseExhaustiveDependencies , self :: use_hook_at_top_level :: UseHookAtTopLevel , self :: use_is_array :: UseIsArray , 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 new file mode 100644 index 000000000000..05af2e3950ed --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_invalid_new_builtin.rs @@ -0,0 +1,107 @@ +use crate::{semantic_services::Semantic, JsRuleAction}; +use biome_analyze::{context::RuleContext, declare_rule, ActionCategory, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_factory::make; +use biome_js_syntax::{ + global_identifier, static_value::StaticValue, AnyJsExpression, JsCallExpression, + JsNewExpression, +}; +use biome_rowan::{chain_trivia_pieces, AstNode, BatchMutationExt}; + +declare_rule! { + /// Disallow `new` operators with global non-constructor functions. + /// + /// Some global functions cannot be called using the new operator and + /// 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) + /// + /// Source: https://eslint.org/docs/latest/rules/no-new-native-nonconstructor/ + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// var foo = new Symbol('foo'); + /// var bar = new BigInt(9007199254740991); + /// ``` + /// + /// ## Valid + /// + /// ```js + /// var foo = Symbol('foo'); + /// var bar = BigInt(9007199254740991); + /// + /// function baz(Symbol) { + /// const qux = new Symbol("baz"); + /// } + /// function quux(BigInt) { + /// const corge = new BigInt(9007199254740991); + /// } + /// ``` + pub(crate) NoInvalidNewBuiltin { + version: "next", + name: "noInvalidNewBuiltin", + recommended: true, + } +} + +impl Rule for NoInvalidNewBuiltin { + type Query = Semantic; + type State = StaticValue; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + 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), + _ => None, + } + } + + fn diagnostic(ctx: &RuleContext, builtin_name: &Self::State) -> Option { + let builtin_name = builtin_name.text(); + Some(RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + markup! { + {builtin_name}" cannot be called as a constructor." + }, + ).note(markup! { + "Calling "{builtin_name}" with the ""new"" operator throws a ""TypeError""." + })) + } + + fn action(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + let call_expression = convert_new_expression_to_call_expression(node)?; + let mut mutation = ctx.root().begin(); + mutation.replace_node_discard_trivia::( + node.clone().into(), + call_expression.into(), + ); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { "Remove ""new""." }.to_owned(), + mutation, + }) + } +} + +fn convert_new_expression_to_call_expression(expr: &JsNewExpression) -> Option { + let new_token = expr.new_token().ok()?; + let mut callee = expr.callee().ok()?; + if new_token.has_leading_comments() || new_token.has_trailing_comments() { + callee = callee.prepend_trivia_pieces(chain_trivia_pieces( + new_token.leading_trivia().pieces(), + new_token.trailing_trivia().pieces(), + ))?; + } + Some(make::js_call_expression(callee, expr.arguments()?).build()) +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js new file mode 100644 index 000000000000..c4bbcfd81b91 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js @@ -0,0 +1,11 @@ +var foo = new Symbol("foo"); +function bar() { + return function Symbol() {}; +} +var baz = new Symbol("baz"); + +var foo = new BigInt(9007199254740991); +function bar() { + return function BigInt() {}; +} +var baz = new BigInt(9007199254740991); 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 new file mode 100644 index 000000000000..c9b440d3a251 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/invalid.js.snap @@ -0,0 +1,103 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +var foo = new Symbol("foo"); +function bar() { + return function Symbol() {}; +} +var baz = new Symbol("baz"); + +var foo = new BigInt(9007199254740991); +function bar() { + return function BigInt() {}; +} +var baz = new BigInt(9007199254740991); + +``` + +# Diagnostics +``` +invalid.js:1:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Symbol cannot be called as a constructor. + + > 1 │ var foo = new Symbol("foo"); + │ ^^^^^^^^^^^^^^^^^ + 2 │ function bar() { + 3 │ return function Symbol() {}; + + i Calling Symbol with the new operator throws a TypeError. + + i Suggested fix: Remove new. + + 1 │ var·foo·=·new·Symbol("foo"); + │ ---- + +``` + +``` +invalid.js:5:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Symbol cannot be called as a constructor. + + 3 │ return function Symbol() {}; + 4 │ } + > 5 │ var baz = new Symbol("baz"); + │ ^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ var foo = new BigInt(9007199254740991); + + i Calling Symbol with the new operator throws a TypeError. + + i Suggested fix: Remove new. + + 5 │ var·baz·=·new·Symbol("baz"); + │ ---- + +``` + +``` +invalid.js:7:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! BigInt cannot be called as a constructor. + + 5 │ var baz = new Symbol("baz"); + 6 │ + > 7 │ var foo = new BigInt(9007199254740991); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │ function bar() { + 9 │ return function BigInt() {}; + + i Calling BigInt with the new operator throws a TypeError. + + i Suggested fix: Remove new. + + 7 │ var·foo·=·new·BigInt(9007199254740991); + │ ---- + +``` + +``` +invalid.js:11:11 lint/nursery/noInvalidNewBuiltin FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! BigInt cannot be called as a constructor. + + 9 │ return function BigInt() {}; + 10 │ } + > 11 │ var baz = new BigInt(9007199254740991); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ + + i Calling BigInt with the new operator throws a TypeError. + + i Suggested fix: Remove new. + + 11 │ var·baz·=·new·BigInt(9007199254740991); + │ ---- + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js new file mode 100644 index 000000000000..7e922006c946 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js @@ -0,0 +1,17 @@ +var foo = Symbol("foo"); +function bar(Symbol) { + var baz = new Symbol("baz"); +} +function Symbol() {} +new Symbol(); +new foo(Symbol); +new foo(bar, Symbol); + +var foo = BigInt(9007199254740991); +function bar(BigInt) { + var baz = new BigInt(9007199254740991); +} +function BigInt() {} +new BigInt(); +new foo(BigInt); +new foo(bar, BigInt); 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 new file mode 100644 index 000000000000..ffa291bc6a0e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInvalidNewBuiltin/valid.js.snap @@ -0,0 +1,27 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +var foo = Symbol("foo"); +function bar(Symbol) { + var baz = new Symbol("baz"); +} +function Symbol() {} +new Symbol(); +new foo(Symbol); +new foo(bar, Symbol); + +var foo = BigInt(9007199254740991); +function bar(BigInt) { + var baz = new BigInt(9007199254740991); +} +function BigInt() {} +new BigInt(); +new foo(BigInt); +new foo(bar, BigInt); + +``` + + diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 1a8b8718b026..cfbdf91e2c9e 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2201,6 +2201,15 @@ 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."] + #[bpaf( + long("no-invalid-new-builtin"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_invalid_new_builtin: Option, #[doc = "Enforce proper usage of new and constructor."] #[bpaf( long("no-misleading-instantiator"), @@ -2264,7 +2273,7 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 17] = [ + pub(crate) const GROUP_RULES: [&'static str; 18] = [ "noAccumulatingSpread", "noConfusingVoidType", "noDuplicateJsonKeys", @@ -2272,6 +2281,7 @@ impl Nursery { "noFallthroughSwitchClause", "noGlobalIsFinite", "noGlobalIsNan", + "noInvalidNewBuiltin", "noMisleadingInstantiator", "noUselessElse", "noVoid", @@ -2283,10 +2293,11 @@ impl Nursery { "useImportRestrictions", "useIsArray", ]; - const RECOMMENDED_RULES: [&'static str; 9] = [ + const RECOMMENDED_RULES: [&'static str; 10] = [ "noDuplicateJsonKeys", "noGlobalIsFinite", "noGlobalIsNan", + "noInvalidNewBuiltin", "noMisleadingInstantiator", "noUselessElse", "useArrowFunction", @@ -2294,18 +2305,19 @@ impl Nursery { "useGroupedTypeImport", "useIsArray", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 9] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 10] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 17] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 18] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2323,6 +2335,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2374,56 +2387,61 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } + if let Some(rule) = self.use_is_array.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -2463,56 +2481,61 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } + if let Some(rule) = self.use_is_array.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2523,10 +2546,10 @@ impl Nursery { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 9] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 10] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 17] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 18] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -2556,6 +2579,7 @@ impl Nursery { "noFallthroughSwitchClause" => self.no_fallthrough_switch_clause.as_ref(), "noGlobalIsFinite" => self.no_global_is_finite.as_ref(), "noGlobalIsNan" => self.no_global_is_nan.as_ref(), + "noInvalidNewBuiltin" => self.no_invalid_new_builtin.as_ref(), "noMisleadingInstantiator" => self.no_misleading_instantiator.as_ref(), "noUselessElse" => self.no_useless_else.as_ref(), "noVoid" => self.no_void.as_ref(), diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index 1aaa62af3c0a..e27430db59ff 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -2030,6 +2030,7 @@ impl VisitNode for Nursery { "noFallthroughSwitchClause", "noGlobalIsFinite", "noGlobalIsNan", + "noInvalidNewBuiltin", "noMisleadingInstantiator", "noUselessElse", "noVoid", @@ -2220,6 +2221,29 @@ impl VisitNode for Nursery { )); } }, + "noInvalidNewBuiltin" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_invalid_new_builtin = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut rule_configuration = RuleConfiguration::default(); + rule_configuration.map_rule_configuration( + &value, + name_text, + "noInvalidNewBuiltin", + diagnostics, + )?; + self.no_invalid_new_builtin = Some(rule_configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noMisleadingInstantiator" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index d131e8a6718d..f0a8be407580 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1016,6 +1016,13 @@ { "type": "null" } ] }, + "noInvalidNewBuiltin": { + "description": "Disallow new operators with global non-constructor functions.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noMisleadingInstantiator": { "description": "Enforce proper usage of new and constructor.", "anyOf": [ diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 73874cfcbdc3..59f3ca316cb8 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -681,6 +681,10 @@ export interface Nursery { * Use Number.isNaN instead of global isNaN. */ noGlobalIsNan?: RuleConfiguration; + /** + * Disallow new operators with global non-constructor functions. + */ + noInvalidNewBuiltin?: RuleConfiguration; /** * Enforce proper usage of new and constructor. */ @@ -1282,6 +1286,7 @@ export type Category = | "lint/nursery/noFallthroughSwitchClause" | "lint/nursery/noGlobalIsFinite" | "lint/nursery/noGlobalIsNan" + | "lint/nursery/noInvalidNewBuiltin" | "lint/nursery/noMisleadingInstantiator" | "lint/nursery/noUselessElse" | "lint/nursery/noVoid" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index d131e8a6718d..f0a8be407580 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1016,6 +1016,13 @@ { "type": "null" } ] }, + "noInvalidNewBuiltin": { + "description": "Disallow new operators with global non-constructor functions.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noMisleadingInstantiator": { "description": "Enforce proper usage of new and constructor.", "anyOf": [ diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 162acf325203..d9ea0fa6e034 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 159 rules

\ No newline at end of file +

Biome's linter has a total of 160 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 33c8f64082ff..75cae4a40042 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -352,6 +352,8 @@ Disallow fallthrough of switch clauses. 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. ### [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 new file mode 100644 index 000000000000..dc1b368c67cd --- /dev/null +++ b/website/src/content/docs/linter/rules/no-invalid-new-builtin.md @@ -0,0 +1,79 @@ +--- +title: noInvalidNewBuiltin (since vnext) +--- + +**Diagnostic Category: `lint/nursery/noInvalidNewBuiltin`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Disallow `new` operators with global non-constructor functions. + +Some global functions cannot be called using the new operator and +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) + +Source: https://eslint.org/docs/latest/rules/no-new-native-nonconstructor/ + +## Examples + +### Invalid + +```jsx +var foo = new Symbol('foo'); +var bar = new BigInt(9007199254740991); +``` + +

nursery/noInvalidNewBuiltin.js:1:11 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
+
+   Symbol cannot be called as a constructor.
+  
+  > 1 │ var foo = new Symbol('foo');
+             ^^^^^^^^^^^^^^^^^
+    2 │ var bar = new BigInt(9007199254740991);
+    3 │ 
+  
+   Calling Symbol with the new operator throws a TypeError.
+  
+   Suggested fix: Remove new.
+  
+    1 │ var·foo·=·new·Symbol('foo');
+            ----              
+nursery/noInvalidNewBuiltin.js:2:11 lint/nursery/noInvalidNewBuiltin  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━
+
+   BigInt cannot be called as a constructor.
+  
+    1 │ var foo = new Symbol('foo');
+  > 2 │ var bar = new BigInt(9007199254740991);
+             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    3 │ 
+  
+   Calling BigInt with the new operator throws a TypeError.
+  
+   Suggested fix: Remove new.
+  
+    2 │ var·bar·=·new·BigInt(9007199254740991);
+            ----                         
+
+ +## Valid + +```jsx +var foo = Symbol('foo'); +var bar = BigInt(9007199254740991); + +function baz(Symbol) { + const qux = new Symbol("baz"); +} +function quux(BigInt) { + const corge = new BigInt(9007199254740991); +} +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)