From 9d69167d962249a375fe61a207504f8e376988b6 Mon Sep 17 00:00:00 2001 From: alakhpc <36898190+alakhpc@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:43:37 +0400 Subject: [PATCH] feat(linter) eslint: no-new-wrappers (#2413) Part of: #479 docs: https://eslint.org/docs/latest/rules/no-new-wrappers I am unsure of what do do with 2 of the eslint tests that use `languageOptions: { globals: { String: "off" } }` and `/* global Boolean:off */`. I've commented them out as of now. --------- Co-authored-by: Boshen --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/no_new_wrappers.rs | 98 +++++++++++++++++++ .../src/snapshots/no_new_wrappers.snap | 44 +++++++++ 3 files changed, 144 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs create mode 100644 crates/oxc_linter/src/snapshots/no_new_wrappers.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 3874e2025..5a70a5e16 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -72,6 +72,7 @@ mod eslint { pub mod no_loss_of_precision; pub mod no_mixed_operators; pub mod no_new_symbol; + pub mod no_new_wrappers; pub mod no_obj_calls; pub mod no_prototype_builtins; pub mod no_redeclare; @@ -370,6 +371,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_loss_of_precision, eslint::no_mixed_operators, eslint::no_new_symbol, + eslint::no_new_wrappers, eslint::no_obj_calls, eslint::no_prototype_builtins, eslint::no_redeclare, diff --git a/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs b/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs new file mode 100644 index 000000000..fe8c54cb1 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs @@ -0,0 +1,98 @@ +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error( + "eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects" +)] +#[diagnostic( + severity(warning), + help("do not use {0} as a constructor, consider removing the new operator.") +)] +struct NoNewWrappersDiagnostic(Atom, #[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoNewWrappers; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow new operators with the String, Number, and Boolean objects + /// + /// ### Why is this bad? + /// + /// The first problem is that primitive wrapper objects are, in fact, objects. That means typeof will return "object" instead of "string", "number", or "boolean". + /// The second problem comes with boolean objects. Every object is truthy, that means an instance of Boolean always resolves to true even when its actual value is false. + /// https://eslint.org/docs/latest/rules/no-new-wrappers + /// + /// ### Example + /// ```javascript + /// var stringObject = new String('Hello world'); + /// ``` + NoNewWrappers, + pedantic +); + +impl Rule for NoNewWrappers { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::NewExpression(expr) = node.kind() else { return }; + let Expression::Identifier(ident) = &expr.callee else { return }; + if (ident.name == "String" || ident.name == "Number" || ident.name == "Boolean") + && ctx.semantic().is_reference_to_global_variable(ident) + { + ctx.diagnostic(NoNewWrappersDiagnostic(ident.name.clone(), expr.span)); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "var a = new Object();", + "var a = String('test'), b = String.fromCharCode(32);", + "function test(Number) { return new Number; }", + r#" + import String from "./string"; + const str = new String(42); + "#, + " + if (foo) { + result = new Boolean(bar); + } else { + var Boolean = CustomBoolean; + } + ", + // Disabled because the eslint-test uses languageOptions: { globals: { String: "off" } } + // "new String()", + + // Disabled as the global option from the eslint-test does not work + // " + // /* global Boolean:off */ + // assert(new Boolean); + // ", + ]; + + let fail = vec![ + "var a = new String('hello');", + "var a = new Number(10);", + "var a = new Boolean(false);", + " + const a = new String('bar'); + { + const String = CustomString; + const b = new String('foo'); + } + ", + ]; + + Tester::new(NoNewWrappers::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_new_wrappers.snap b/crates/oxc_linter/src/snapshots/no_new_wrappers.snap new file mode 100644 index 000000000..d9ec4e672 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_new_wrappers.snap @@ -0,0 +1,44 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_new_wrappers +--- + + ⚠ eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects + ╭─[no_new_wrappers.tsx:1:9] + 1 │ var a = new String('hello'); + · ─────────────────── + ╰──── + help: do not use String as a constructor, consider removing the new operator. + + ⚠ eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects + ╭─[no_new_wrappers.tsx:1:9] + 1 │ var a = new Number(10); + · ────────────── + ╰──── + help: do not use Number as a constructor, consider removing the new operator. + + ⚠ eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects + ╭─[no_new_wrappers.tsx:1:9] + 1 │ var a = new Boolean(false); + · ────────────────── + ╰──── + help: do not use Boolean as a constructor, consider removing the new operator. + + ⚠ eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects + ╭─[no_new_wrappers.tsx:2:23] + 1 │ + 2 │ const a = new String('bar'); + · ───────────────── + 3 │ { + ╰──── + help: do not use String as a constructor, consider removing the new operator. + + ⚠ eslint(no-new-wrappers): Disallow new operators with the String, Number, and Boolean objects + ╭─[no_new_wrappers.tsx:5:27] + 4 │ const String = CustomString; + 5 │ const b = new String('foo'); + · ───────────────── + 6 │ } + ╰──── + help: do not use String as a constructor, consider removing the new operator. +