diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 25250be91..e01b97d67 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -597,17 +597,17 @@ impl Argument<'_> { } impl<'a> AssignmentTarget<'a> { - pub fn get_identifier(&self) -> Option<&str> { - self.as_simple_assignment_target().and_then(|it| it.get_identifier()) + pub fn get_identifier(&self) -> Option<&'a str> { + self.as_simple_assignment_target().and_then(SimpleAssignmentTarget::get_identifier) } pub fn get_expression(&self) -> Option<&Expression<'a>> { - self.as_simple_assignment_target().and_then(|it| it.get_expression()) + self.as_simple_assignment_target().and_then(SimpleAssignmentTarget::get_expression) } } impl<'a> SimpleAssignmentTarget<'a> { - pub fn get_identifier(&self) -> Option<&str> { + pub fn get_identifier(&self) -> Option<&'a str> { match self { Self::AssignmentTargetIdentifier(ident) => Some(ident.name.as_str()), match_member_expression!(Self) => self.to_member_expression().static_property_name(), diff --git a/crates/oxc_linter/src/fixer/fix.rs b/crates/oxc_linter/src/fixer/fix.rs index e2e65c0c8..abd4cccdd 100644 --- a/crates/oxc_linter/src/fixer/fix.rs +++ b/crates/oxc_linter/src/fixer/fix.rs @@ -33,6 +33,8 @@ bitflags! { const None = 0; const SafeFix = Self::Fix.bits(); const DangerousFix = Self::Dangerous.bits() | Self::Fix.bits(); + /// Fixes and Suggestions that are safe or dangerous. + const All = Self::Dangerous.bits() | Self::Fix.bits() | Self::Suggestion.bits(); } } diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 80cb760cd..e54d5756d 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -7,10 +7,25 @@ use oxc_ast::ast::{ use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; +use oxc_semantic::AstNodeId; use oxc_span::{Atom, Span}; +use oxc_syntax::identifier::is_identifier_name; +use phf::phf_set; use crate::{context::LintContext, rule::Rule, AstNode}; +fn named_diagnostic(function_name: &str, span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Unexpected named {function_name}.")) + .with_label(span) + .with_help("Remove the name on this function expression.") +} + +fn unnamed_diagnostic(inferred_name_or_description: &str, span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Unexpected unnamed {inferred_name_or_description}.")) + .with_label(span) + .with_help("Consider giving this function expression a name.") +} + #[derive(Debug, Default, Clone)] pub struct FuncNames { default_config: FuncNamesConfig, @@ -125,7 +140,7 @@ declare_oxc_lint!( /// ``` FuncNames, style, - pending + conditional_fix_suggestion ); /// Determines whether the current FunctionExpression node is a get, set, or @@ -422,24 +437,85 @@ impl Rule for FuncNames { let func_name = get_function_name(func); let func_name_complete = get_function_name_with_kind(func, parent_node); + let span = Span::new(func.span.start, func.params.span.start); if func_name.is_some() { - ctx.diagnostic( - OxcDiagnostic::warn(format!("Unexpected named {func_name_complete}.")) - .with_label(Span::new(func.span.start, func.params.span.start)), + ctx.diagnostic_with_suggestion( + named_diagnostic(&func_name_complete, span), + |fixer| func.id.as_ref().map_or(fixer.noop(), |id| fixer.delete(id)), ); } else { - ctx.diagnostic( - OxcDiagnostic::warn(format!("Unexpected unnamed {func_name_complete}.")) - .with_label(Span::new(func.span.start, func.params.span.start)), - ); + ctx.diagnostic_with_fix(unnamed_diagnostic(&func_name_complete, span), |fixer| { + guess_function_name(ctx, parent_node.id()).map_or_else( + || fixer.noop(), + |name| fixer.insert_text_after(&span, format!(" {name}")), + ) + }); } } } } +fn guess_function_name<'a>(ctx: &LintContext<'a>, parent_id: AstNodeId) -> Option> { + for parent_kind in ctx.nodes().iter_parents(parent_id).map(AstNode::kind) { + match parent_kind { + AstKind::ParenthesizedExpression(_) + | AstKind::TSAsExpression(_) + | AstKind::TSNonNullExpression(_) + | AstKind::TSSatisfiesExpression(_) => continue, + AstKind::AssignmentExpression(assign) => { + return assign.left.get_identifier().map(Cow::Borrowed) + } + AstKind::VariableDeclarator(decl) => { + return decl.id.get_identifier().as_ref().map(Atom::as_str).map(Cow::Borrowed) + } + AstKind::ObjectProperty(prop) => { + return prop.key.static_name().and_then(|name| { + if is_valid_identifier_name(&name) { + Some(name) + } else { + None + } + }) + } + AstKind::PropertyDefinition(prop) => { + return prop.key.static_name().and_then(|name| { + if is_valid_identifier_name(&name) { + Some(name) + } else { + None + } + }) + } + _ => return None, + } + } + None +} + +const INVALID_NAMES: phf::set::Set<&'static str> = phf_set! { + "arguments", + "async", + "await", + "constructor", + "default", + "eval", + "null", + "undefined", + "yield", +}; + +fn is_valid_identifier_name(name: &str) -> bool { + !INVALID_NAMES.contains(name) && is_identifier_name(name) +} + #[test] fn test() { use crate::tester::Tester; + use serde_json::json; + + let always = Some(json!(["always"])); + let as_needed = Some(json!(["as-needed"])); + let never = Some(json!(["never"])); let pass = vec![ ("Foo.prototype.bar = function bar(){};", None), @@ -450,107 +526,74 @@ fn test() { ("exports = { get foo() { return 1; }, set bar(val) { return val; } };", None), ("({ foo() { return 1; } });", None), // { "ecmaVersion": 6 }, ("class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", None), // { "ecmaVersion": 6 }, - ("function foo() {}", Some(serde_json::json!(["always"]))), - ("var a = function foo() {};", Some(serde_json::json!(["always"]))), + ("function foo() {}", always.clone()), + ("var a = function foo() {};", always.clone()), ( "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", - Some(serde_json::json!(["as-needed"])), + as_needed.clone(), ), // { "ecmaVersion": 6 }, - ("({ foo() {} });", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("var foo = function(){};", Some(serde_json::json!(["as-needed"]))), - ("({foo: function(){}});", Some(serde_json::json!(["as-needed"]))), - ("(foo = function(){});", Some(serde_json::json!(["as-needed"]))), - ("({foo = function(){}} = {});", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("({key: foo = function(){}} = {});", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("[foo = function(){}] = [];", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("function fn(foo = function(){}) {}", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("function foo() {}", Some(serde_json::json!(["never"]))), - ("var a = function() {};", Some(serde_json::json!(["never"]))), - ("var a = function foo() { foo(); };", Some(serde_json::json!(["never"]))), - ("var foo = {bar: function() {}};", Some(serde_json::json!(["never"]))), - ("$('#foo').click(function() {});", Some(serde_json::json!(["never"]))), - ("Foo.prototype.bar = function() {};", Some(serde_json::json!(["never"]))), + ("({ foo() {} });", as_needed.clone()), // { "ecmaVersion": 6 }, + ("var foo = function(){};", as_needed.clone()), + ("({foo: function(){}});", as_needed.clone()), + ("(foo = function(){});", as_needed.clone()), + ("({foo = function(){}} = {});", as_needed.clone()), // { "ecmaVersion": 6 }, + ("({key: foo = function(){}} = {});", as_needed.clone()), // { "ecmaVersion": 6 }, + ("[foo = function(){}] = [];", as_needed.clone()), // { "ecmaVersion": 6 }, + ("function fn(foo = function(){}) {}", as_needed.clone()), // { "ecmaVersion": 6 }, + ("function foo() {}", never.clone()), + ("var a = function() {};", never.clone()), + ("var a = function foo() { foo(); };", never.clone()), + ("var foo = {bar: function() {}};", never.clone()), + ("$('#foo').click(function() {});", never.clone()), + ("Foo.prototype.bar = function() {};", never.clone()), ( "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", - Some(serde_json::json!(["never"])), + never.clone(), ), // { "ecmaVersion": 6 }, - ("({ foo() {} });", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 6 }, - ("export default function foo() {}", Some(serde_json::json!(["always"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("export default function foo() {}", Some(serde_json::json!(["as-needed"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("export default function foo() {}", Some(serde_json::json!(["never"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("export default function() {}", Some(serde_json::json!(["never"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("var foo = bar(function *baz() {});", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 6 }, + ("({ foo() {} });", never.clone()), // { "ecmaVersion": 6 }, + ("export default function foo() {}", always.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("export default function foo() {}", as_needed.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("export default function foo() {}", never.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("export default function() {}", never.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", always.clone()), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", Some(json!(["always", { "generators": "always" }]))), // { "ecmaVersion": 6 }, ( "var foo = bar(function *baz() {});", - Some(serde_json::json!(["always", { "generators": "always" }])), + Some(json!(["always", { "generators": "as-needed" }])), + ), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["always", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", as_needed.clone()), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", as_needed.clone()), // { "ecmaVersion": 6 }, + ( + "var foo = bar(function *baz() {});", + Some(json!(["as-needed", { "generators": "always" }])), ), // { "ecmaVersion": 6 }, ( "var foo = bar(function *baz() {});", - Some(serde_json::json!(["always", { "generators": "as-needed" }])), + Some(json!(["as-needed", { "generators": "as-needed" }])), ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["always", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ("var foo = bar(function *baz() {});", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("var foo = function*() {};", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["as-needed", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", Some(json!(["never", { "generators": "always" }]))), // { "ecmaVersion": 6 }, ( "var foo = bar(function *baz() {});", - Some(serde_json::json!(["as-needed", { "generators": "always" }])), + Some(json!(["never", { "generators": "as-needed" }])), ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *baz() {});", - Some(serde_json::json!(["as-needed", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["as-needed", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *baz() {});", - Some(serde_json::json!(["never", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *baz() {});", - Some(serde_json::json!(["never", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["never", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ("var foo = bar(function *() {});", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 6 }, - ("var foo = function*() {};", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["never", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["never", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["never", { "generators": "never" }]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["always", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["always", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["always", { "generators": "never" }]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["as-needed", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["as-needed", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["as-needed", { "generators": "never" }]))), // { "ecmaVersion": 6 }, - ("class C { foo = function() {}; }", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 2022 }, - ("class C { [foo] = function() {}; }", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 2022 }, - ("class C { #foo = function() {}; }", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 2022 } + ("var foo = function*() {};", Some(json!(["never", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", never.clone()), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", never.clone()), // { "ecmaVersion": 6 }, + ("(function*() {}())", never.clone()), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["never", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["never", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["never", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["always", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["always", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["always", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["as-needed", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["as-needed", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["as-needed", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("class C { foo = function() {}; }", as_needed.clone()), // { "ecmaVersion": 2022 }, + ("class C { [foo] = function() {}; }", as_needed.clone()), // { "ecmaVersion": 2022 }, + ("class C { #foo = function() {}; }", as_needed.clone()), // { "ecmaVersion": 2022 } ]; let fail = vec![ @@ -560,92 +603,133 @@ fn test() { ("var a = new Date(function() {});", None), ("var test = function(d, e, f) {};", None), ("new function() {}", None), - ("Foo.prototype.bar = function() {};", Some(serde_json::json!(["as-needed"]))), - ("(function(){}())", Some(serde_json::json!(["as-needed"]))), - ("f(function(){})", Some(serde_json::json!(["as-needed"]))), - ("var a = new Date(function() {});", Some(serde_json::json!(["as-needed"]))), - ("new function() {}", Some(serde_json::json!(["as-needed"]))), - ("var {foo} = function(){};", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("({ a: obj.prop = function(){} } = foo);", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("[obj.prop = function(){}] = foo;", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("var { a: [b] = function(){} } = foo;", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("function foo({ a } = function(){}) {};", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("var x = function foo() {};", Some(serde_json::json!(["never"]))), - ("Foo.prototype.bar = function foo() {};", Some(serde_json::json!(["never"]))), - ("({foo: function foo() {}})", Some(serde_json::json!(["never"]))), - ("export default function() {}", Some(serde_json::json!(["always"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("export default function() {}", Some(serde_json::json!(["as-needed"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("export default (function(){});", Some(serde_json::json!(["as-needed"]))), // { "sourceType": "module", "ecmaVersion": 6 }, - ("var foo = bar(function *() {});", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 6 }, - ("var foo = function*() {};", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 6 }, + ("Foo.prototype.bar = function() {};", as_needed.clone()), + ("(function(){}())", as_needed.clone()), + ("f(function(){})", as_needed.clone()), + ("var a = new Date(function() {});", as_needed.clone()), + ("new function() {}", as_needed.clone()), + ("var {foo} = function(){};", as_needed.clone()), // { "ecmaVersion": 6 }, + ("({ a: obj.prop = function(){} } = foo);", as_needed.clone()), // { "ecmaVersion": 6 }, + ("[obj.prop = function(){}] = foo;", as_needed.clone()), // { "ecmaVersion": 6 }, + ("var { a: [b] = function(){} } = foo;", as_needed.clone()), // { "ecmaVersion": 6 }, + ("function foo({ a } = function(){}) {};", as_needed.clone()), // { "ecmaVersion": 6 }, + ("var x = function foo() {};", never.clone()), + ("Foo.prototype.bar = function foo() {};", never.clone()), + ("({foo: function foo() {}})", never.clone()), + ("export default function() {}", always.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("export default function() {}", as_needed.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("export default (function(){});", as_needed.clone()), // { "sourceType": "module", "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", always.clone()), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", always.clone()), // { "ecmaVersion": 6 }, + ("(function*() {}())", always.clone()), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["always", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["always", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["always", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["always", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["always", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", as_needed.clone()), // { "ecmaVersion": 6 }, + ("(function*() {}())", as_needed.clone()), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["as-needed", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["as-needed", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["as-needed", { "generators": "always" }]))), // { "ecmaVersion": 6 }, ( "var foo = bar(function *() {});", - Some(serde_json::json!(["always", { "generators": "always" }])), + Some(json!(["as-needed", { "generators": "as-needed" }])), ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["always", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["always", { "generators": "always" }]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["always", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["always", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, - ("var foo = bar(function *() {});", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["as-needed", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["as-needed", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["as-needed", { "generators": "always" }]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["as-needed", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ( - "(function*() {}())", - Some(serde_json::json!(["as-needed", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["never", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = function*() {};", - Some(serde_json::json!(["never", { "generators": "always" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["never", { "generators": "always" }]))), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *() {});", - Some(serde_json::json!(["never", { "generators": "as-needed" }])), - ), // { "ecmaVersion": 6 }, - ("(function*() {}())", Some(serde_json::json!(["never", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, - ("var foo = bar(function *baz() {});", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["as-needed", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["never", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("var foo = function*() {};", Some(json!(["never", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["never", { "generators": "always" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *() {});", Some(json!(["never", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("(function*() {}())", Some(json!(["never", { "generators": "as-needed" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", never.clone()), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", Some(json!(["never", { "generators": "never" }]))), // { "ecmaVersion": 6 }, + ("var foo = bar(function *baz() {});", Some(json!(["always", { "generators": "never" }]))), // { "ecmaVersion": 6 }, ( "var foo = bar(function *baz() {});", - Some(serde_json::json!(["never", { "generators": "never" }])), + Some(json!(["as-needed", { "generators": "never" }])), ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *baz() {});", - Some(serde_json::json!(["always", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ( - "var foo = bar(function *baz() {});", - Some(serde_json::json!(["as-needed", { "generators": "never" }])), - ), // { "ecmaVersion": 6 }, - ("class C { foo = function() {} }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2022 }, - ("class C { public foo = function() {} }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2022 }, - ("class C { [foo] = function() {} }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2022 }, - ("class C { #foo = function() {} }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2022 }, - ("class C { foo = bar(function() {}) }", Some(serde_json::json!(["as-needed"]))), // { "ecmaVersion": 2022 }, - ("class C { foo = function bar() {} }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2022 } + ("class C { foo = function() {} }", always.clone()), // { "ecmaVersion": 2022 }, + ("class C { public foo = function() {} }", always.clone()), // { "ecmaVersion": 2022 }, + ("class C { [foo] = function() {} }", always.clone()), // { "ecmaVersion": 2022 }, + ("class C { #foo = function() {} }", always.clone()), // { "ecmaVersion": 2022 }, + ("class C { foo = bar(function() {}) }", as_needed.clone()), // { "ecmaVersion": 2022 }, + ("class C { foo = function bar() {} }", never.clone()), // { "ecmaVersion": 2022 } ]; - Tester::new(FuncNames::NAME, pass, fail).test_and_snapshot(); + let fix = vec![ + // lb + ("const foo = function() {}", "const foo = function foo() {}", always.clone()), + ( + "Foo.prototype.bar = function() {}", + "Foo.prototype.bar = function bar() {}", + always.clone(), + ), + ("let foo; foo = function() {}", "let foo; foo = function foo() {}", always.clone()), + ( + "class C { public foo = function() {} }", + "class C { public foo = function foo() {} }", + always.clone(), + ), + ( + "class C { public ['foo'] = function() {} }", + "class C { public ['foo'] = function foo() {} }", + always.clone(), + ), + ( + "class C { public [`foo`] = function() {} }", + "class C { public [`foo`] = function foo() {} }", + always.clone(), + ), + ( + "class C { public ['invalid identifier name'] = function() {} }", + "class C { public ['invalid identifier name'] = function() {} }", + always.clone(), + ), + ( + "class C { public [foo] = function() {} }", + "class C { public [foo] = function() {} }", + always.clone(), + ), + ( + "class C { public [undefined] = function() {} }", + "class C { public [undefined] = function() {} }", + always.clone(), + ), + ( + "class C { public [null] = function() {} }", + "class C { public [null] = function() {} }", + always.clone(), + ), + ( + "class C { public ['undefined'] = function() {} }", + "class C { public ['undefined'] = function() {} }", + always.clone(), + ), + ( + "class C { public ['null'] = function() {} }", + "class C { public ['null'] = function() {} }", + always.clone(), + ), + ( + "const x = { foo: function() {} }", + "const x = { foo: function foo() {} }", + always.clone(), + ), + ( + "const x = { ['foo']: function() {} }", + "const x = { ['foo']: function foo() {} }", + always.clone(), + ), + // suggest removal when configured to "never" + ("const foo = function foo() {}", "const foo = function () {}", never.clone()), + ( + "Foo.prototype.bar = function bar() {}", + "Foo.prototype.bar = function () {}", + never.clone(), + ), + ("class C { foo = function foo() {} }", "class C { foo = function () {} }", never.clone()), + ]; + + Tester::new(FuncNames::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/func_names.snap b/crates/oxc_linter/src/snapshots/func_names.snap index c1c9e3bdd..743107794 100644 --- a/crates/oxc_linter/src/snapshots/func_names.snap +++ b/crates/oxc_linter/src/snapshots/func_names.snap @@ -6,309 +6,361 @@ source: crates/oxc_linter/src/tester.rs 1 │ Foo.prototype.bar = function() {}; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:12] 1 │ var test = function(d, e, f) {}; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:5] 1 │ new function() {} · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function() {}; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:5] 1 │ new function() {} · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:13] 1 │ var {foo} = function(){}; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ ({ a: obj.prop = function(){} } = foo); · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:13] 1 │ [obj.prop = function(){}] = foo; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ var { a: [b] = function(){} } = foo; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:22] 1 │ function foo({ a } = function(){}) {}; · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:9] 1 │ var x = function foo() {}; · ──────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function foo() {}; · ──────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:8] 1 │ ({foo: function foo() {}}) · ──────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ export default function() {} · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ export default function() {} · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:17] 1 │ export default (function(){}); · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── + help: Remove the name on this function expression. ⚠ eslint(func-names): Unexpected unnamed method foo. ╭─[func_names.tsx:1:17] 1 │ class C { foo = function() {} } · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed public method foo. ╭─[func_names.tsx:1:24] 1 │ class C { public foo = function() {} } · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed method. ╭─[func_names.tsx:1:19] 1 │ class C { [foo] = function() {} } · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed private method foo. ╭─[func_names.tsx:1:18] 1 │ class C { #foo = function() {} } · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:21] 1 │ class C { foo = bar(function() {}) } · ──────── ╰──── + help: Consider giving this function expression a name. ⚠ eslint(func-names): Unexpected named method foo. ╭─[func_names.tsx:1:17] 1 │ class C { foo = function bar() {} } · ──────────── ╰──── + help: Remove the name on this function expression. diff --git a/crates/oxc_linter/src/tester.rs b/crates/oxc_linter/src/tester.rs index c35d7dc6d..46c602950 100644 --- a/crates/oxc_linter/src/tester.rs +++ b/crates/oxc_linter/src/tester.rs @@ -277,7 +277,7 @@ impl Tester { let allocator = Allocator::default(); let rule = self.find_rule().read_json(rule_config.unwrap_or_default()); let options = LintOptions::default() - .with_fix(is_fix.then_some(FixKind::DangerousFix).unwrap_or_default()) + .with_fix(is_fix.then_some(FixKind::All).unwrap_or_default()) .with_import_plugin(self.import_plugin) .with_jest_plugin(self.jest_plugin) .with_vitest_plugin(self.vitest_plugin)