Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new no-new-native-nonconstructor rule #16368

Merged
merged 15 commits into from Nov 6, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/src/_data/further_reading_links.json
Expand Up @@ -698,5 +698,19 @@
"logo": "https://eslint.org/apple-touch-icon.png",
"title": "Interesting bugs caught by no-constant-binary-expression - ESLint - Pluggable JavaScript Linter",
"description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease."
},
"https://tc39.es/ecma262/#sec-symbol-constructor": {
"domain": "tc39.es",
"url": "https://tc39.es/ecma262/#sec-symbol-constructor",
"logo": "https://tc39.es/ecma262/img/favicon.ico",
"title": "ECMAScript® 2023 Language Specification",
"description": null
},
"https://tc39.es/ecma262/#sec-bigint-constructor": {
"domain": "tc39.es",
"url": "https://tc39.es/ecma262/#sec-bigint-constructor",
"logo": "https://tc39.es/ecma262/img/favicon.ico",
"title": "ECMAScript® 2023 Language Specification",
"description": null
}
}
74 changes: 74 additions & 0 deletions docs/src/rules/no-new-native-nonconstructor.md
@@ -0,0 +1,74 @@
---
title: no-new-native-nonconstructor
layout: doc
rule_type: problem
related_rules:
- no-new-symbol
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
further_reading:
- https://tc39.es/ecma262/#sec-symbol-constructor
- https://tc39.es/ecma262/#sec-bigint-constructor
---



Certain functions are not intended to be used with the `new` operator, but to be called as a function.
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved

```js
var foo = new Symbol("foo");
```

```js
var bar = new BigInt(9007199254740991)
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
```

These throw a `TypeError` exception.
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved

## Rule Details

This rule is aimed at preventing the accidental calling of certain functions with the `new` operator. These functions are:
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved

* `Symbol`
* `BigInt`

## Examples

Examples of **incorrect** code for this rule:

::: incorrect

```js
/*eslint no-new-native-nonconstructor: "error"*/
/*eslint-env es2022*/

var foo = new Symbol('foo');
var bar = new BigInt(9007199254740991);
```

:::

Examples of **correct** code for this rule:

::: correct

```js
/*eslint no-new-symbol: "error"*/
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
/*eslint-env es2022*/

var foo = Symbol('foo');
var bar = BigInt(9007199254740991);

// Ignores shadowed Symbol.
function baz(Symbol) {
const qux = new Symbol("baz");
}
function quux(BigInt) {
const corge = new BigInt(9007199254740991);
}

```

:::

## When Not To Use It

This rule should not be used in ES3/5 environments.
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -167,6 +167,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-nested-ternary": () => require("./no-nested-ternary"),
"no-new": () => require("./no-new"),
"no-new-func": () => require("./no-new-func"),
"no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
"no-new-object": () => require("./no-new-object"),
"no-new-require": () => require("./no-new-require"),
"no-new-symbol": () => require("./no-new-symbol"),
Expand Down
64 changes: 64 additions & 0 deletions lib/rules/no-new-native-nonconstructor.js
@@ -0,0 +1,64 @@
/**
* @fileoverview Rule to disallow use of the new operator with global no constructor functions
* @author Sosuke Suzuki
*/

"use strict";

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"];

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",

docs: {
description: "Disallow `new` operators with global no constructor functions",
sosukesuzuki marked this conversation as resolved.
Show resolved Hide resolved
mdjermanovic marked this conversation as resolved.
Show resolved Hide resolved
recommended: false,
url: "https://eslint.org/docs/rules/no-new-native-nonconstructor"
},

schema: [],

messages: {
noNewNonconstructor: "`{{name}}` cannot be called as a constructor."
}
},

create(context) {

return {
"Program:exit"() {
const globalScope = context.getScope();

for (const nonConstructorName of nonConstructorGlobalFunctionNames) {
const variable = globalScope.set.get(nonConstructorName);

if (variable && variable.defs.length === 0) {
variable.references.forEach(ref => {
const node = ref.identifier;
const parent = node.parent;

if (parent && parent.type === "NewExpression" && parent.callee === node) {
context.report({
node,
messageId: "noNewNonconstructor",
data: { name: nonConstructorName }
});
}
});
}
}
}
};

}
};
68 changes: 68 additions & 0 deletions tests/lib/rules/no-new-native-nonconstructor.js
@@ -0,0 +1,68 @@
/**
* @fileoverview Tests for the no-new-native-nonconstructor rule
* @author Sosuke Suzuki
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-new-native-nonconstructor"),
{ RuleTester } = require("../../../lib/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ env: { es2022: true } });

ruleTester.run("no-new-native-nonconstructor", rule, {
valid: [

// Symbol
"var foo = Symbol('foo');",
"function bar(Symbol) { var baz = new Symbol('baz');}",
"function Symbol() {} new Symbol();",
"new foo(Symbol);",
"new foo(bar, Symbol);",

// BigInt
"var foo = BigInt(9007199254740991);",
"function bar(BigInt) { var baz = new BigInt(9007199254740991);}",
"function BigInt() {} new BigInt();",
"new foo(BigInt);",
"new foo(bar, BigInt);"
],
invalid: [

// Symbol
{
code: "var foo = new Symbol('foo');",
errors: [{
message: "`Symbol` cannot be called as a constructor."
}]
},
{
code: "function bar() { return function Symbol() {}; } var baz = new Symbol('baz');",
errors: [{
message: "`Symbol` cannot be called as a constructor."
}]
},

// BigInt
{
code: "var foo = new BigInt(9007199254740991);",
errors: [{
message: "`BigInt` cannot be called as a constructor."
}]
},
{
code: "function bar() { return function BigInt() {}; } var baz = new BigInt(9007199254740991);",
errors: [{
message: "`BigInt` cannot be called as a constructor."
}]
}
]
});
1 change: 1 addition & 0 deletions tools/rule-types.json
Expand Up @@ -154,6 +154,7 @@
"no-nested-ternary": "suggestion",
"no-new": "suggestion",
"no-new-func": "suggestion",
"no-new-native-nonconstructor": "problem",
"no-new-object": "suggestion",
"no-new-require": "suggestion",
"no-new-symbol": "problem",
Expand Down