Skip to content

Commit

Permalink
feat(lint/noInvalidNewBuiltin): add rule (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasweng authored Sep 23, 2023
1 parent bb84871 commit c8ff3d9
Show file tree
Hide file tree
Showing 15 changed files with 446 additions and 30 deletions.
1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/semantic_analyzers/nursery.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<JsNewExpression>;
type State = StaticValue;
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> 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<Self>, builtin_name: &Self::State) -> Option<RuleDiagnostic> {
let builtin_name = builtin_name.text();
Some(RuleDiagnostic::new(
rule_category!(),
ctx.query().range(),
markup! {
<Emphasis>{builtin_name}</Emphasis>" cannot be called as a constructor."
},
).note(markup! {
"Calling "<Emphasis>{builtin_name}</Emphasis>" with the "<Emphasis>"new"</Emphasis>" operator throws a "<Emphasis>"TypeError"</Emphasis>"."
}))
}

fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
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::<AnyJsExpression>(
node.clone().into(),
call_expression.into(),
);
Some(JsRuleAction {
category: ActionCategory::QuickFix,
applicability: Applicability::MaybeIncorrect,
message: markup! { "Remove "<Emphasis>"new"</Emphasis>"." }.to_owned(),
mutation,
})
}
}

fn convert_new_expression_to_call_expression(expr: &JsNewExpression) -> Option<JsCallExpression> {
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())
}
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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);
│ ----
```


Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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);

```


Loading

0 comments on commit c8ff3d9

Please sign in to comment.