Skip to content

Commit f14587c

Browse files
Sosuke Suzukimdjermanovicnzakasbtmills
authored
feat: new no-new-native-nonconstructor rule (#16368)
* feat: add `no-new-noconstructor` rule * docs: add docs for `no-new-noconstructor` * fix: pass constructor name to message * docs: add `related_rules` * chore: add new rule * fix: rename `noconstructor` -> `nonconstructor` * fix: set `recommended: false` * fix: rename `no-new-nonconstructor` -> `no-new-native-nonconstructor` * docs: fix correct example * Update docs/src/rules/no-new-native-nonconstructor.md Co-authored-by: Milos Djermanovic <[email protected]> * docs: update links * docs: address reviews * fix: `no constructor` -> `non-constructor` * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas <[email protected]> Co-authored-by: Milos Djermanovic <[email protected]> Co-authored-by: Nicholas C. Zakas <[email protected]> Co-authored-by: Brandon Mills <[email protected]>
1 parent 978799b commit f14587c

6 files changed

Lines changed: 221 additions & 0 deletions

File tree

docs/src/_data/further_reading_links.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,5 +705,19 @@
705705
"logo": "https://github.com/fluidicon.png",
706706
"title": "GitHub - tc39/proposal-class-static-block: ECMAScript class static initialization blocks",
707707
"description": "ECMAScript class static initialization blocks. Contribute to tc39/proposal-class-static-block development by creating an account on GitHub."
708+
},
709+
"https://tc39.es/ecma262/#sec-symbol-constructor": {
710+
"domain": "tc39.es",
711+
"url": "https://tc39.es/ecma262/#sec-symbol-constructor",
712+
"logo": "https://tc39.es/ecma262/img/favicon.ico",
713+
"title": "ECMAScript® 2023 Language Specification",
714+
"description": null
715+
},
716+
"https://tc39.es/ecma262/#sec-bigint-constructor": {
717+
"domain": "tc39.es",
718+
"url": "https://tc39.es/ecma262/#sec-bigint-constructor",
719+
"logo": "https://tc39.es/ecma262/img/favicon.ico",
720+
"title": "ECMAScript® 2023 Language Specification",
721+
"description": null
708722
}
709723
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
title: no-new-native-nonconstructor
3+
layout: doc
4+
rule_type: problem
5+
related_rules:
6+
- no-obj-calls
7+
further_reading:
8+
- https://tc39.es/ecma262/#sec-symbol-constructor
9+
- https://tc39.es/ecma262/#sec-bigint-constructor
10+
---
11+
12+
13+
14+
It is a convention in JavaScript that global variables beginning with an uppercase letter typically represent classes that can be instantiated using the `new` operator, such as `new Array` and `new Map`. Confusingly, JavaScript also provides some global variables that begin with an uppercase letter that cannot be called using the `new` operator and will throw an error if you attempt to do so. These are typically functions that are related to data types and are easy to mistake for classes. Consider the following example:
15+
16+
```js
17+
// throws a TypeError
18+
let foo = new Symbol("foo");
19+
20+
// throws a TypeError
21+
let result = new BigInt(9007199254740991);
22+
23+
Both `new Symbol` and `new BigInt` throw a type error because they are functions and not classes. It is easy to make this mistake by assuming the uppercase letters indicate classes.
24+
25+
## Rule Details
26+
27+
This rule is aimed at preventing the accidental calling of native JavaScript global functions with the `new` operator. These functions are:
28+
29+
* `Symbol`
30+
* `BigInt`
31+
32+
## Examples
33+
34+
Examples of **incorrect** code for this rule:
35+
36+
::: incorrect
37+
38+
```js
39+
/*eslint no-new-native-nonconstructor: "error"*/
40+
/*eslint-env es2022*/
41+
42+
var foo = new Symbol('foo');
43+
var bar = new BigInt(9007199254740991);
44+
```
45+
46+
:::
47+
48+
Examples of **correct** code for this rule:
49+
50+
::: correct
51+
52+
```js
53+
/*eslint no-new-native-nonconstructor: "error"*/
54+
/*eslint-env es2022*/
55+
56+
var foo = Symbol('foo');
57+
var bar = BigInt(9007199254740991);
58+
59+
// Ignores shadowed Symbol.
60+
function baz(Symbol) {
61+
const qux = new Symbol("baz");
62+
}
63+
function quux(BigInt) {
64+
const corge = new BigInt(9007199254740991);
65+
}
66+
67+
```
68+
69+
:::
70+
71+
## When Not To Use It
72+
73+
This rule should not be used in ES3/5 environments.

lib/rules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
168168
"no-nested-ternary": () => require("./no-nested-ternary"),
169169
"no-new": () => require("./no-new"),
170170
"no-new-func": () => require("./no-new-func"),
171+
"no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
171172
"no-new-object": () => require("./no-new-object"),
172173
"no-new-require": () => require("./no-new-require"),
173174
"no-new-symbol": () => require("./no-new-symbol"),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @fileoverview Rule to disallow use of the new operator with global non-constructor functions
3+
* @author Sosuke Suzuki
4+
*/
5+
6+
"use strict";
7+
8+
//------------------------------------------------------------------------------
9+
// Helpers
10+
//------------------------------------------------------------------------------
11+
12+
const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"];
13+
14+
//------------------------------------------------------------------------------
15+
// Rule Definition
16+
//------------------------------------------------------------------------------
17+
18+
/** @type {import('../shared/types').Rule} */
19+
module.exports = {
20+
meta: {
21+
type: "problem",
22+
23+
docs: {
24+
description: "Disallow `new` operators with global non-constructor functions",
25+
recommended: false,
26+
url: "https://eslint.org/docs/rules/no-new-native-nonconstructor"
27+
},
28+
29+
schema: [],
30+
31+
messages: {
32+
noNewNonconstructor: "`{{name}}` cannot be called as a constructor."
33+
}
34+
},
35+
36+
create(context) {
37+
38+
return {
39+
"Program:exit"() {
40+
const globalScope = context.getScope();
41+
42+
for (const nonConstructorName of nonConstructorGlobalFunctionNames) {
43+
const variable = globalScope.set.get(nonConstructorName);
44+
45+
if (variable && variable.defs.length === 0) {
46+
variable.references.forEach(ref => {
47+
const node = ref.identifier;
48+
const parent = node.parent;
49+
50+
if (parent && parent.type === "NewExpression" && parent.callee === node) {
51+
context.report({
52+
node,
53+
messageId: "noNewNonconstructor",
54+
data: { name: nonConstructorName }
55+
});
56+
}
57+
});
58+
}
59+
}
60+
}
61+
};
62+
63+
}
64+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @fileoverview Tests for the no-new-native-nonconstructor rule
3+
* @author Sosuke Suzuki
4+
*/
5+
6+
"use strict";
7+
8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const rule = require("../../../lib/rules/no-new-native-nonconstructor"),
13+
{ RuleTester } = require("../../../lib/rule-tester");
14+
15+
//------------------------------------------------------------------------------
16+
// Tests
17+
//------------------------------------------------------------------------------
18+
19+
const ruleTester = new RuleTester({ env: { es2022: true } });
20+
21+
ruleTester.run("no-new-native-nonconstructor", rule, {
22+
valid: [
23+
24+
// Symbol
25+
"var foo = Symbol('foo');",
26+
"function bar(Symbol) { var baz = new Symbol('baz');}",
27+
"function Symbol() {} new Symbol();",
28+
"new foo(Symbol);",
29+
"new foo(bar, Symbol);",
30+
31+
// BigInt
32+
"var foo = BigInt(9007199254740991);",
33+
"function bar(BigInt) { var baz = new BigInt(9007199254740991);}",
34+
"function BigInt() {} new BigInt();",
35+
"new foo(BigInt);",
36+
"new foo(bar, BigInt);"
37+
],
38+
invalid: [
39+
40+
// Symbol
41+
{
42+
code: "var foo = new Symbol('foo');",
43+
errors: [{
44+
message: "`Symbol` cannot be called as a constructor."
45+
}]
46+
},
47+
{
48+
code: "function bar() { return function Symbol() {}; } var baz = new Symbol('baz');",
49+
errors: [{
50+
message: "`Symbol` cannot be called as a constructor."
51+
}]
52+
},
53+
54+
// BigInt
55+
{
56+
code: "var foo = new BigInt(9007199254740991);",
57+
errors: [{
58+
message: "`BigInt` cannot be called as a constructor."
59+
}]
60+
},
61+
{
62+
code: "function bar() { return function BigInt() {}; } var baz = new BigInt(9007199254740991);",
63+
errors: [{
64+
message: "`BigInt` cannot be called as a constructor."
65+
}]
66+
}
67+
]
68+
});

tools/rule-types.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
"no-nested-ternary": "suggestion",
156156
"no-new": "suggestion",
157157
"no-new-func": "suggestion",
158+
"no-new-native-nonconstructor": "problem",
158159
"no-new-object": "suggestion",
159160
"no-new-require": "suggestion",
160161
"no-new-symbol": "problem",

0 commit comments

Comments
 (0)