Skip to content

Commit 432cd77

Browse files
committed
feat(linter/no-new-wrapper): implement auto-fixer (#10680)
- Implement auto fixer for `eslint/no-new-wrapper` - Report `new Symbol(expr)` as a violation. Diagnostics for this case warn that `Symbol` is not a constructor. - Limit length of report spans to 24, falling back to underlying only `new Builtin`
1 parent c89da93 commit 432cd77

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1-
use oxc_ast::{AstKind, ast::Expression};
1+
use oxc_ast::{
2+
AstKind,
3+
ast::{Argument, Expression, NewExpression},
4+
};
25
use oxc_diagnostics::OxcDiagnostic;
36
use oxc_macros::declare_oxc_lint;
47
use oxc_span::Span;
58

6-
use crate::{AstNode, context::LintContext, rule::Rule};
9+
use crate::{
10+
AstNode,
11+
context::LintContext,
12+
fixer::{RuleFix, RuleFixer},
13+
rule::Rule,
14+
};
715

816
fn no_new_wrappers_diagnostic(builtin_name: &str, new_span: Span) -> OxcDiagnostic {
917
OxcDiagnostic::warn(format!("Do not use `{builtin_name}` as a constructor"))
1018
.with_help("Remove the `new` operator.")
1119
.with_label(new_span)
1220
}
1321

22+
fn not_a_constructor_diagnostic(builtin_name: &str, new_span: Span) -> OxcDiagnostic {
23+
OxcDiagnostic::warn(format!("`{builtin_name}` is not a constructor"))
24+
.with_help("Remove the `new` operator.")
25+
.with_label(new_span)
26+
}
27+
1428
#[derive(Debug, Default, Clone)]
1529
pub struct NoNewWrappers;
1630

@@ -36,6 +50,7 @@ declare_oxc_lint!(
3650
/// var stringObject = new String('Hello world');
3751
/// var numberObject = new Number(33);
3852
/// var booleanObject = new Boolean(false);
53+
/// var symbolObject = new Symbol('foo'); // symbol is not a constructor
3954
/// ```
4055
///
4156
/// Examples of **correct** code for this rule:
@@ -44,11 +59,12 @@ declare_oxc_lint!(
4459
/// var stringObject2 = String(value);
4560
/// var numberObject = Number(value);
4661
/// var booleanObject = Boolean(value);
62+
/// var symbolObject = Symbol('foo');
4763
/// ```
4864
NoNewWrappers,
4965
eslint,
5066
pedantic,
51-
pending
67+
fix,
5268
);
5369

5470
impl Rule for NoNewWrappers {
@@ -59,14 +75,51 @@ impl Rule for NoNewWrappers {
5975
let Expression::Identifier(ident) = &expr.callee else {
6076
return;
6177
};
62-
if (ident.name == "String" || ident.name == "Number" || ident.name == "Boolean")
63-
&& ctx.is_reference_to_global_variable(ident)
64-
{
65-
ctx.diagnostic(no_new_wrappers_diagnostic(ident.name.as_str(), expr.span));
78+
if !ctx.is_reference_to_global_variable(ident) {
79+
return;
80+
}
81+
let name = ident.name.as_str();
82+
let span = if expr.span.size() > 24 {
83+
Span::new(expr.span.start, ident.span.end)
84+
} else {
85+
expr.span
86+
};
87+
match name {
88+
"String" | "Number" | "Boolean" => {
89+
ctx.diagnostic_with_fix(no_new_wrappers_diagnostic(name, span), |fixer| {
90+
remove_new_operator(fixer, expr, name)
91+
});
92+
}
93+
"Symbol" => {
94+
ctx.diagnostic_with_fix(not_a_constructor_diagnostic(name, span), |fixer| {
95+
remove_new_operator(fixer, expr, name)
96+
});
97+
}
98+
_ => {}
6699
}
67100
}
68101
}
69102

103+
fn remove_new_operator<'a>(
104+
fixer: RuleFixer<'_, 'a>,
105+
expr: &NewExpression<'a>,
106+
name: &'a str,
107+
) -> RuleFix<'a> {
108+
debug_assert!(expr.callee.is_identifier_reference());
109+
let remove_new_fix = fixer.delete_range(Span::sized(expr.span.start, 4 /* "new " */));
110+
111+
let Some(arg) = expr.arguments.first().and_then(Argument::as_expression) else {
112+
return remove_new_fix;
113+
};
114+
115+
match (name, arg.get_inner_expression()) {
116+
("Boolean", Expression::BooleanLiteral(lit)) => fixer.replace_with(expr, lit.as_ref()),
117+
("String", Expression::StringLiteral(lit)) => fixer.replace_with(expr, lit.as_ref()),
118+
("Number", Expression::NumericLiteral(lit)) => fixer.replace_with(expr, lit.as_ref()),
119+
_ => remove_new_fix,
120+
}
121+
}
122+
70123
#[test]
71124
fn test() {
72125
use crate::tester::Tester;
@@ -107,7 +160,29 @@ fn test() {
107160
const b = new String('foo');
108161
}
109162
",
163+
"
164+
var a = new String('wow look at me im a really long string, ' +
165+
'it sure would be annoying if this whole thing ' +
166+
'was underlined instead of just the constructor');
167+
",
168+
];
169+
let fix = vec![
170+
("var a = new Number(foo);", "var a = Number(foo);"),
171+
("var a = new Number(1 + 1);", "var a = Number(1 + 1);"),
172+
("var a = new String(foo);", "var a = String(foo);"),
173+
("var a = new Boolean(foo);", "var a = Boolean(foo);"),
174+
("var a = new Boolean(!!x);", "var a = Boolean(!!x);"),
175+
("var a = new Symbol('foo');", "var a = Symbol('foo');"),
176+
// literals dont need to be wrapped
177+
("var a = new Boolean(false);", "var a = false;"),
178+
("var a = new Boolean(true);", "var a = true;"),
179+
("var a = new String('hello');", "var a = 'hello';"),
180+
("var a = new String((((('hello')))));", "var a = 'hello';"),
181+
("var a = new Number(10);", "var a = 10;"),
182+
("var a = new Number(10 as number);", "var a = 10;"),
110183
];
111184

112-
Tester::new(NoNewWrappers::NAME, NoNewWrappers::PLUGIN, pass, fail).test_and_snapshot();
185+
Tester::new(NoNewWrappers::NAME, NoNewWrappers::PLUGIN, pass, fail)
186+
.expect_fix(fix)
187+
.test_and_snapshot();
113188
}

crates/oxc_linter/src/snapshots/eslint_no_new_wrappers.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ source: crates/oxc_linter/src/tester.rs
3939
6 │ }
4040
╰────
4141
help: Remove the `new` operator.
42+
43+
eslint(no-new-wrappers): Do not use `String` as a constructor
44+
╭─[no_new_wrappers.tsx:2:21]
45+
1
46+
2var a = new String('wow look at me im a really long string, ' +
47+
· ──────────
48+
3'it sure would be annoying if this whole thing ' +
49+
╰────
50+
help: Remove the `new` operator.

0 commit comments

Comments
 (0)