Skip to content

Commit

Permalink
handle non-constructor built-in objects
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasweng committed Sep 22, 2023
1 parent 5a8a6f4 commit 38964a6
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,64 @@ 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:
///
/// - [`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
///
/// ### 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 {
Expand All @@ -59,38 +87,40 @@ 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<Self>,
builtin_fn_name: &Self::State,
) -> Option<RuleDiagnostic> {
fn diagnostic(ctx: &RuleContext<Self>, builtin_name: &Self::State) -> Option<RuleDiagnostic> {
Some(RuleDiagnostic::new(
rule_category!(),
ctx.query().range(),
markup! {
<Emphasis>"`"{builtin_fn_name.text()}"`"</Emphasis>" cannot be called as a constructor."
<Emphasis>"`"{builtin_name.text()}"`"</Emphasis>" cannot be called as a constructor."
},
))
}

fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
fn action(ctx: &RuleContext<Self>, builtin_name: &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,
})
match builtin_name.text() {
"Symbol" | "BigInt" => Some(JsRuleAction {
category: ActionCategory::QuickFix,
applicability: Applicability::MaybeIncorrect,
message: markup! { "Remove "<Emphasis>"`new`"</Emphasis>"." }.to_owned(),
mutation,
}),
_ => None,
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand All @@ -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 │
```


Loading

0 comments on commit 38964a6

Please sign in to comment.