Skip to content

Commit b5bc900

Browse files
committed
feat(linter): implement fixer for unicorn/no-new-buffer (#19326)
1 parent 8d3ae27 commit b5bc900

File tree

1 file changed

+76
-6
lines changed

1 file changed

+76
-6
lines changed

crates/oxc_linter/src/rules/unicorn/no_new_buffer.rs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
use oxc_ast::{AstKind, ast::Expression};
1+
use oxc_ast::{
2+
AstKind,
3+
ast::{Argument, Expression},
4+
};
25
use oxc_diagnostics::OxcDiagnostic;
36
use oxc_macros::declare_oxc_lint;
4-
use oxc_span::Span;
7+
use oxc_semantic::IsGlobalReference;
8+
use oxc_span::{GetSpan, Span};
59

610
use crate::{AstNode, context::LintContext, rule::Rule};
711

@@ -37,7 +41,7 @@ declare_oxc_lint!(
3741
NoNewBuffer,
3842
unicorn,
3943
pedantic,
40-
pending
44+
suggestion
4145
);
4246

4347
impl Rule for NoNewBuffer {
@@ -49,10 +53,54 @@ impl Rule for NoNewBuffer {
4953
let Expression::Identifier(ident) = &new_expr.callee.without_parentheses() else {
5054
return;
5155
};
52-
if ident.name != "Buffer" {
56+
if ident.name != "Buffer" || !ident.is_global_reference(ctx.scoping()) {
5357
return;
5458
}
55-
ctx.diagnostic(no_new_buffer_diagnostic(ident.span));
59+
60+
// Determine which method to use based on argument type
61+
let method = determine_buffer_method(new_expr);
62+
let expr_span = new_expr.span;
63+
64+
ctx.diagnostic_with_suggestion(no_new_buffer_diagnostic(ident.span), |fixer| {
65+
let Some(method) = method else {
66+
return fixer.noop();
67+
};
68+
69+
// Build arguments string
70+
let args_text = new_expr
71+
.arguments
72+
.iter()
73+
.map(|arg| ctx.source_range(arg.span()))
74+
.collect::<Vec<_>>()
75+
.join(", ");
76+
77+
let replacement = format!("Buffer.{method}({args_text})");
78+
fixer.replace(expr_span, replacement)
79+
});
80+
}
81+
}
82+
83+
/// Determines which Buffer method to use based on the first argument.
84+
/// Returns `Some("alloc")` for numeric arguments, `Some("from")` for array/string arguments,
85+
/// or `None` if the type can't be determined (unsafe to fix).
86+
fn determine_buffer_method(new_expr: &oxc_ast::ast::NewExpression) -> Option<&'static str> {
87+
// Handle spread arguments - unsafe to fix
88+
if new_expr.arguments.iter().any(Argument::is_spread) {
89+
return None;
90+
}
91+
92+
let first_arg = new_expr.arguments.first()?.as_expression()?;
93+
let first_arg = first_arg.without_parentheses();
94+
95+
match first_arg {
96+
// Numeric literals → Buffer.alloc
97+
Expression::NumericLiteral(_) => Some("alloc"),
98+
// String/template literals → Buffer.from
99+
Expression::StringLiteral(_)
100+
| Expression::TemplateLiteral(_)
101+
| Expression::ArrayExpression(_) => Some("from"),
102+
// For other expressions, we can't safely determine the type
103+
_ => None,
56104
}
57105
}
58106

@@ -67,6 +115,7 @@ fn test() {
67115
r"const buffer = Buffer.from('7468697320697320612074c3a97374', 'hex')",
68116
r"const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])",
69117
r"const buffer = Buffer.alloc(10)",
118+
r"const Buffer = function () {}; new Buffer(10);",
70119
];
71120

72121
let fail = vec![
@@ -86,5 +135,26 @@ fn test() {
86135
r"new Buffer(input, encoding);",
87136
];
88137

89-
Tester::new(NoNewBuffer::NAME, NoNewBuffer::PLUGIN, pass, fail).test_and_snapshot();
138+
let fix = vec![
139+
// Numeric argument → Buffer.alloc
140+
(r"const buffer = new Buffer(10);", r"const buffer = Buffer.alloc(10);"),
141+
// String argument → Buffer.from
142+
(r#"const buffer = new Buffer("string");"#, r#"const buffer = Buffer.from("string");"#),
143+
(
144+
r#"const buffer = new Buffer("7468697320697320612074c3a97374", "hex")"#,
145+
r#"const buffer = Buffer.from("7468697320697320612074c3a97374", "hex")"#,
146+
),
147+
// Array argument → Buffer.from
148+
(
149+
r"const buffer = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])",
150+
r"const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])",
151+
),
152+
(r"const buffer = new Buffer([0x62, bar])", r"const buffer = Buffer.from([0x62, bar])"),
153+
// Template literal → Buffer.from
154+
(r"const buffer = new Buffer(`${unknown}`)", r"const buffer = Buffer.from(`${unknown}`)"),
155+
];
156+
157+
Tester::new(NoNewBuffer::NAME, NoNewBuffer::PLUGIN, pass, fail)
158+
.expect_fix(fix)
159+
.test_and_snapshot();
90160
}

0 commit comments

Comments
 (0)