From 3cf6824219af406097df56d181c52eddda9a0842 Mon Sep 17 00:00:00 2001 From: Sysix Date: Fri, 2 Aug 2024 22:57:43 +0200 Subject: [PATCH 01/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/eslint/func_names.rs | 434 ++++++++++++++++++ .../oxc_linter/src/snapshots/func_names.snap | 308 +++++++++++++ 3 files changed, 744 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/func_names.rs create mode 100644 crates/oxc_linter/src/snapshots/func_names.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 358594e8b6d99..200b8b2bf9634 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -32,6 +32,7 @@ mod eslint { pub mod default_param_last; pub mod eqeqeq; pub mod for_direction; + pub mod func_names; pub mod getter_return; pub mod guard_for_in; pub mod max_classes_per_file; @@ -457,6 +458,7 @@ oxc_macros::declare_all_lint_rules! { eslint::default_param_last, eslint::eqeqeq, eslint::for_direction, + eslint::func_names, eslint::getter_return, eslint::guard_for_in, eslint::max_classes_per_file, diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs new file mode 100644 index 0000000000000..171cf66ac8f08 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -0,0 +1,434 @@ +use oxc_ast::ast::{ + AssignmentTarget, BindingPatternKind, Expression, Function, FunctionType, PropertyKind, +}; +use oxc_ast::AstKind; + +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Default, Clone)] +pub struct FuncNames { + default_config: FuncNamesConfig, + generators_config: FuncNamesConfig, +} + +#[derive(Debug, Default, Clone, PartialEq)] +enum FuncNamesConfig { + #[default] + Always, + AsNeeded, + Never, +} + +impl FuncNamesConfig { + pub fn from(raw: &str) -> Self { + match raw { + "always" => FuncNamesConfig::Always, + "as-needed" => FuncNamesConfig::AsNeeded, + "never" => FuncNamesConfig::Never, + _ => FuncNamesConfig::default(), + } + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// + /// ### Why is this bad? + /// + /// + /// ### Example + /// ```javascript + /// ``` + FuncNames, + nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + // See for details + + pending // TODO: describe fix capabilities. Remove if no fix can be done, + // keep at 'pending' if you think one could be added but don't know how. + // Options are 'fix', 'fix-dangerous', 'suggestion', and 'suggestion-dangerous' +); +/** + * Determines whether the current FunctionExpression node is a get, set, or + * shorthand method in an object literal or a class. + */ +fn is_object_or_class_method(parent_node: Option<&AstNode>) -> bool { + if parent_node.is_none() { + return false; + } + + match parent_node.unwrap().kind() { + AstKind::MethodDefinition(_) => true, + AstKind::ObjectProperty(property) => { + property.method + || property.kind == PropertyKind::Get + || property.kind == PropertyKind::Set + } + + _ => false, + } +} +/** + * Determines whether the current FunctionExpression node has a name that would be + * inferred from context in a conforming ES6 environment. + */ +fn has_inferred_name(function: &Function, parent_node: Option<&AstNode>) -> bool { + if is_object_or_class_method(parent_node) { + return true; + } + + if parent_node.is_none() { + return false; + } + + match parent_node.unwrap().kind() { + AstKind::VariableDeclarator(declarator) => { + if let BindingPatternKind::BindingIdentifier(_) = declarator.id.kind { + if let Expression::FunctionExpression(function_expression) = + declarator.init.as_ref().unwrap() + { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + } + + false + } + AstKind::ObjectProperty(property) => { + if let Expression::FunctionExpression(function_expression) = &property.value { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + + false + } + AstKind::PropertyDefinition(definition) => { + if let Expression::FunctionExpression(function_expression) = + definition.value.as_ref().unwrap() + { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + + false + } + AstKind::AssignmentExpression(expression) => { + if let AssignmentTarget::AssignmentTargetIdentifier(_) = expression.left { + if let Expression::FunctionExpression(function_expression) = &expression.right { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + } + + false + } + AstKind::AssignmentTargetWithDefault(target) => { + if let AssignmentTarget::AssignmentTargetIdentifier(_) = target.binding { + if let Expression::FunctionExpression(function_expression) = &target.init { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + } + + false + } + AstKind::AssignmentPattern(pattern) => { + if let BindingPatternKind::BindingIdentifier(_) = pattern.left.kind { + if let Expression::FunctionExpression(function_expression) = &pattern.right { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + } + + false + } + _ => false, + } +} + +fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&Span> { + if let Some(id) = &func.id { + return Some(&id.span); + } + + None +} + +fn get_function_name<'a>(func: &'a Function<'a>) -> Option<&Atom<'a>> { + if let Some(id) = &func.id { + return Some(&id.name); + } + + None +} + +impl Rule for FuncNames { + fn from_configuration(value: serde_json::Value) -> Self { + let obj1 = value.get(0); + let obj2: Option<&serde_json::Value> = value.get(1); + + let default_config = + obj1.and_then(serde_json::Value::as_str).map(FuncNamesConfig::from).unwrap_or_default(); + + return Self { + default_config: default_config.clone(), + + generators_config: obj2 + .and_then(|v| v.get("generators")) + .and_then(serde_json::Value::as_str) + .map(FuncNamesConfig::from) + .unwrap_or(default_config), + }; + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::Function(func) = node.kind() { + let parent_node = ctx.nodes().parent_node(node.id()); + + let func_name = get_function_name(func); + let config = + if func.generator { &self.generators_config } else { &self.default_config }; + + if *config == FuncNamesConfig::Never + && func_name.is_some() + && func.r#type != FunctionType::FunctionDeclaration + { + ctx.diagnostic( + OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) + .with_label(Span::new( + func.span.start, + func.body.as_ref().unwrap().span.start, + )), + ); + } else if (*config == FuncNamesConfig::AsNeeded + && func_name.is_none() + && !has_inferred_name(func, parent_node)) + // always + || (*config == FuncNamesConfig::Always + && func_name.is_none() + && !is_object_or_class_method(parent_node)) + { + ctx.diagnostic(OxcDiagnostic::warn("Unexpected unnamed.").with_label(Span::new( + func.span.start, + func.body.as_ref().unwrap().span.start, + ))); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("Foo.prototype.bar = function bar(){};", None), + ("Foo.prototype.bar = () => {}", None), // { "ecmaVersion": 6 }, + ("function foo(){}", None), + ("function test(d, e, f) {}", None), + ("new function bar(){}", None), + ("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"]))), + ( + "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", + Some(serde_json::json!(["as-needed"])), + ), // { "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 }, -- ToDo: expecting AstKind::AssignmentPattern, but getting AstKind::ObjectAssignmentTarget + ("({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"]))), + ( + "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", + Some(serde_json::json!(["never"])), + ), // { "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 }, + ( + "var foo = bar(function *baz() {});", + Some(serde_json::json!(["always", { "generators": "always" }])), + ), // { "ecmaVersion": 6 }, + ( + "var foo = bar(function *baz() {});", + Some(serde_json::json!(["always", { "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 = bar(function *baz() {});", + Some(serde_json::json!(["as-needed", { "generators": "always" }])), + ), // { "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 } + ]; + + let fail = vec![ + ("Foo.prototype.bar = function() {};", None), + ("(function(){}())", None), + ("f(function(){})", None), + ("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 }, + // ("({key: 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 }, + ( + "var foo = bar(function *() {});", + Some(serde_json::json!(["always", { "generators": "always" }])), + ), // { "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 }, + ( + "var foo = bar(function *baz() {});", + Some(serde_json::json!(["never", { "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 { [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 } + ]; + + Tester::new(FuncNames::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/func_names.snap b/crates/oxc_linter/src/snapshots/func_names.snap new file mode 100644 index 0000000000000..6b22cb175ed8c --- /dev/null +++ b/crates/oxc_linter/src/snapshots/func_names.snap @@ -0,0 +1,308 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:21] + 1 │ Foo.prototype.bar = function() {}; + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function(){}()) + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:3] + 1 │ f(function(){}) + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:18] + 1 │ var a = new Date(function() {}); + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:12] + 1 │ var test = function(d, e, f) {}; + · ────────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:5] + 1 │ new function() {} + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:21] + 1 │ Foo.prototype.bar = function() {}; + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function(){}()) + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:3] + 1 │ f(function(){}) + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:18] + 1 │ var a = new Date(function() {}); + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:5] + 1 │ new function() {} + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:13] + 1 │ var {foo} = function(){}; + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:18] + 1 │ ({ a: obj.prop = function(){} } = foo); + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:13] + 1 │ [obj.prop = function(){}] = foo; + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:16] + 1 │ var { a: [b] = function(){} } = foo; + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:22] + 1 │ function foo({ a } = function(){}) {}; + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "foo". + ╭─[func_names.tsx:1:9] + 1 │ var x = function foo() {}; + · ─────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "foo". + ╭─[func_names.tsx:1:21] + 1 │ Foo.prototype.bar = function foo() {}; + · ─────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "foo". + ╭─[func_names.tsx:1:8] + 1 │ ({foo: function foo() {}}) + · ─────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:16] + 1 │ export default function() {} + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:16] + 1 │ export default function() {} + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:17] + 1 │ export default (function(){}); + · ────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:11] + 1 │ var foo = function*() {}; + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:11] + 1 │ var foo = function*() {}; + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:11] + 1 │ var foo = function*() {}; + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:11] + 1 │ var foo = function*() {}; + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *() {}); + · ───────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:2] + 1 │ (function*() {}()) + · ──────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "baz". + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *baz() {}); + · ──────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "baz". + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *baz() {}); + · ──────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "baz". + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *baz() {}); + · ──────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "baz". + ╭─[func_names.tsx:1:15] + 1 │ var foo = bar(function *baz() {}); + · ──────────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:17] + 1 │ class C { foo = function() {} } + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:19] + 1 │ class C { [foo] = function() {} } + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:18] + 1 │ class C { #foo = function() {} } + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected unnamed. + ╭─[func_names.tsx:1:21] + 1 │ class C { foo = bar(function() {}) } + · ─────────── + ╰──── + + ⚠ eslint(func-names): Unexpected named "bar". + ╭─[func_names.tsx:1:17] + 1 │ class C { foo = function bar() {} } + · ─────────────── + ╰──── From 238afabb0a1e0702920f750798f885856cc92731 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 15:21:36 +0200 Subject: [PATCH 02/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 118 ++++++++++++++---- 1 file changed, 96 insertions(+), 22 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 171cf66ac8f08..0076339d83a84 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -1,8 +1,8 @@ use oxc_ast::ast::{ - AssignmentTarget, BindingPatternKind, Expression, Function, FunctionType, PropertyKind, + AssignmentTarget, AssignmentTargetProperty, BindingPatternKind, Expression, Function, + FunctionType, PropertyKind, }; use oxc_ast::AstKind; - use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{Atom, Span}; @@ -146,11 +146,27 @@ fn has_inferred_name(function: &Function, parent_node: Option<&AstNode>) -> bool false } + AstKind::ObjectAssignmentTarget(target) => { + for property in &target.properties { + if let AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(identifier) = + property + { + if let Expression::FunctionExpression(function_expression) = + identifier.init.as_ref().unwrap() + { + return get_function_identifier(function_expression) + == get_function_identifier(function); + } + } + } + + false + } _ => false, } } -fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&Span> { +fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&'a Span> { if let Some(id) = &func.id { return Some(&id.span); } @@ -166,10 +182,37 @@ fn get_function_name<'a>(func: &'a Function<'a>) -> Option<&Atom<'a>> { None } +fn is_invalid_function( + func: &Function, + config: &FuncNamesConfig, + parent_node: Option<&AstNode>, +) -> bool { + let func_name = get_function_name(func); + + if + // never + (*config == FuncNamesConfig::Never + && func_name.is_some() + && func.r#type != FunctionType::FunctionDeclaration) + // as needed + || (*config == FuncNamesConfig::AsNeeded + && func_name.is_none() + && !has_inferred_name(func, parent_node)) + // always + || (*config == FuncNamesConfig::Always + && func_name.is_none() + && !is_object_or_class_method(parent_node)) + { + return true; + } + + false +} + impl Rule for FuncNames { fn from_configuration(value: serde_json::Value) -> Self { let obj1 = value.get(0); - let obj2: Option<&serde_json::Value> = value.get(1); + let obj2 = value.get(1); let default_config = obj1.and_then(serde_json::Value::as_str).map(FuncNamesConfig::from).unwrap_or_default(); @@ -184,18 +227,56 @@ impl Rule for FuncNames { .unwrap_or(default_config), }; } - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - if let AstKind::Function(func) = node.kind() { - let parent_node = ctx.nodes().parent_node(node.id()); + fn run_once(&self, ctx: &LintContext<'_>) { + let mut invalid_funcs: Vec<&Function> = vec![]; + for node in ctx.nodes().iter() { + match node.kind() { + // check function if it invalid, do not report it because maybe later the function is calling itself + AstKind::Function(func) => { + let parent_node = ctx.nodes().parent_node(node.id()); + let config = + if func.generator { &self.generators_config } else { &self.default_config }; + + if is_invalid_function(func, config, parent_node) { + invalid_funcs.push(func); + } + } + + // check if the calling function is inside of its own body + // when yes remove it from invalid_funcs because recursion are always named + AstKind::CallExpression(expression) => { + if let Expression::Identifier(idendifier) = &expression.callee { + let ast_span = ctx.nodes().iter_parents(node.id()).skip(1).find_map(|p| { + match p.kind() { + AstKind::Function(func) => { + let func_name = get_function_name(func); + + func_name?; + + if *func_name.unwrap() == idendifier.name { + return Some(func.span); + } + + None + } + _ => None, + } + }); + + if let Some(span) = ast_span { + invalid_funcs.retain(|func| func.span != span); + } + } + } + _ => {} + } + } + + for func in &invalid_funcs { let func_name = get_function_name(func); - let config = - if func.generator { &self.generators_config } else { &self.default_config }; - if *config == FuncNamesConfig::Never - && func_name.is_some() - && func.r#type != FunctionType::FunctionDeclaration - { + if func_name.is_some() { ctx.diagnostic( OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) .with_label(Span::new( @@ -203,14 +284,7 @@ impl Rule for FuncNames { func.body.as_ref().unwrap().span.start, )), ); - } else if (*config == FuncNamesConfig::AsNeeded - && func_name.is_none() - && !has_inferred_name(func, parent_node)) - // always - || (*config == FuncNamesConfig::Always - && func_name.is_none() - && !is_object_or_class_method(parent_node)) - { + } else { ctx.diagnostic(OxcDiagnostic::warn("Unexpected unnamed.").with_label(Span::new( func.span.start, func.body.as_ref().unwrap().span.start, @@ -243,7 +317,7 @@ fn test() { ("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 }, -- ToDo: expecting AstKind::AssignmentPattern, but getting AstKind::ObjectAssignmentTarget + ("({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 }, From cc5d77b1cd4353dfa02014cdfb587f7a393bed5c Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 15:38:42 +0200 Subject: [PATCH 03/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 0076339d83a84..4474c6921f97d 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -37,20 +37,22 @@ impl FuncNamesConfig { declare_oxc_lint!( /// ### What it does /// + /// Require or disallow named function expressions /// /// ### Why is this bad? /// + /// If you leave off the function name then when the function throws an exception you are likely + /// to get something similar to anonymous function in the stack trace. If you provide the optional + /// name for a function expression then you will get the name of the function expression in the stack trace. + /// + /// /// ### Example /// - /// ### Example /// ```javascript + /// Foo.prototype.bar = function bar() {}; /// ``` FuncNames, - nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` - // See for details - - pending // TODO: describe fix capabilities. Remove if no fix can be done, - // keep at 'pending' if you think one could be added but don't know how. - // Options are 'fix', 'fix-dangerous', 'suggestion', and 'suggestion-dangerous' + style, + pending ); /** * Determines whether the current FunctionExpression node is a get, set, or @@ -246,7 +248,7 @@ impl Rule for FuncNames { // check if the calling function is inside of its own body // when yes remove it from invalid_funcs because recursion are always named AstKind::CallExpression(expression) => { - if let Expression::Identifier(idendifier) = &expression.callee { + if let Expression::Identifier(identifier) = &expression.callee { let ast_span = ctx.nodes().iter_parents(node.id()).skip(1).find_map(|p| { match p.kind() { AstKind::Function(func) => { @@ -254,7 +256,7 @@ impl Rule for FuncNames { func_name?; - if *func_name.unwrap() == idendifier.name { + if *func_name.unwrap() == identifier.name { return Some(func.span); } From c91718119df05f76bd4716a8663486bd9c630dd5 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 15:52:24 +0200 Subject: [PATCH 04/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 4 +- .../oxc_linter/src/snapshots/func_names.snap | 102 +++++++++--------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 4474c6921f97d..64b62f87bf398 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -283,13 +283,13 @@ impl Rule for FuncNames { OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) .with_label(Span::new( func.span.start, - func.body.as_ref().unwrap().span.start, + func.id.clone().unwrap().span.end )), ); } else { ctx.diagnostic(OxcDiagnostic::warn("Unexpected unnamed.").with_label(Span::new( func.span.start, - func.body.as_ref().unwrap().span.start, + func.params.span.start ))); } } diff --git a/crates/oxc_linter/src/snapshots/func_names.snap b/crates/oxc_linter/src/snapshots/func_names.snap index 6b22cb175ed8c..7e948716ed2f3 100644 --- a/crates/oxc_linter/src/snapshots/func_names.snap +++ b/crates/oxc_linter/src/snapshots/func_names.snap @@ -4,305 +4,305 @@ source: crates/oxc_linter/src/tester.rs ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function() {}; - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:12] 1 │ var test = function(d, e, f) {}; - · ────────────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:5] 1 │ new function() {} - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function() {}; - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:5] 1 │ new function() {} - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:13] 1 │ var {foo} = function(){}; - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:18] 1 │ ({ a: obj.prop = function(){} } = foo); - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:13] 1 │ [obj.prop = function(){}] = foo; - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:16] 1 │ var { a: [b] = function(){} } = foo; - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:22] 1 │ function foo({ a } = function(){}) {}; - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected named "foo". ╭─[func_names.tsx:1:9] 1 │ var x = function foo() {}; - · ─────────────── + · ──────────── ╰──── ⚠ eslint(func-names): Unexpected named "foo". ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function foo() {}; - · ─────────────── + · ──────────── ╰──── ⚠ eslint(func-names): Unexpected named "foo". ╭─[func_names.tsx:1:8] 1 │ ({foo: function foo() {}}) - · ─────────────── + · ──────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:16] 1 │ export default function() {} - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:16] 1 │ export default function() {} - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:17] 1 │ export default (function(){}); - · ────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); - · ───────────── + · ────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) - · ──────────── + · ───────── ╰──── ⚠ eslint(func-names): Unexpected named "baz". ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); - · ──────────────── + · ───────────── ╰──── ⚠ eslint(func-names): Unexpected named "baz". ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); - · ──────────────── + · ───────────── ╰──── ⚠ eslint(func-names): Unexpected named "baz". ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); - · ──────────────── + · ───────────── ╰──── ⚠ eslint(func-names): Unexpected named "baz". ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); - · ──────────────── + · ───────────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:17] 1 │ class C { foo = function() {} } - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:19] 1 │ class C { [foo] = function() {} } - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:18] 1 │ class C { #foo = function() {} } - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected unnamed. ╭─[func_names.tsx:1:21] 1 │ class C { foo = bar(function() {}) } - · ─────────── + · ──────── ╰──── ⚠ eslint(func-names): Unexpected named "bar". ╭─[func_names.tsx:1:17] 1 │ class C { foo = function bar() {} } - · ─────────────── + · ──────────── ╰──── From d9f5a2f5b274fe88667180369f5750d808ce52e1 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 15:52:50 +0200 Subject: [PATCH 05/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 64b62f87bf398..1e9f2024e860e 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -281,16 +281,13 @@ impl Rule for FuncNames { if func_name.is_some() { ctx.diagnostic( OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) - .with_label(Span::new( - func.span.start, - func.id.clone().unwrap().span.end - )), + .with_label(Span::new(func.span.start, func.id.clone().unwrap().span.end)), ); } else { - ctx.diagnostic(OxcDiagnostic::warn("Unexpected unnamed.").with_label(Span::new( - func.span.start, - func.params.span.start - ))); + ctx.diagnostic( + OxcDiagnostic::warn("Unexpected unnamed.") + .with_label(Span::new(func.span.start, func.params.span.start)), + ); } } } From eff40648972b553162407c4372a05e9a22e47af1 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 15:54:03 +0200 Subject: [PATCH 06/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 1e9f2024e860e..1acb518107db4 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -281,7 +281,7 @@ impl Rule for FuncNames { if func_name.is_some() { ctx.diagnostic( OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) - .with_label(Span::new(func.span.start, func.id.clone().unwrap().span.end)), + .with_label(Span::new(func.span.start, func.params.span.start)), ); } else { ctx.diagnostic( From 8a77cae5784fa4a128958da6b220b2dddab2c9c3 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 20:25:13 +0200 Subject: [PATCH 07/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 156 +++++++++++++++++- .../oxc_linter/src/snapshots/func_names.snap | 102 ++++++------ 2 files changed, 200 insertions(+), 58 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 1acb518107db4..1da933befb828 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -1,6 +1,6 @@ use oxc_ast::ast::{ AssignmentTarget, AssignmentTargetProperty, BindingPatternKind, Expression, Function, - FunctionType, PropertyKind, + FunctionType, MethodDefinitionKind, PropertyKey, PropertyKind, }; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; @@ -168,6 +168,9 @@ fn has_inferred_name(function: &Function, parent_node: Option<&AstNode>) -> bool } } +/** + * Gets the identifier for the function + */ fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&'a Span> { if let Some(id) = &func.id { return Some(&id.span); @@ -176,6 +179,9 @@ fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&'a Span> { None } +/** + * Gets the identifier name of the function + */ fn get_function_name<'a>(func: &'a Function<'a>) -> Option<&Atom<'a>> { if let Some(id) = &func.id { return Some(&id.name); @@ -184,6 +190,141 @@ fn get_function_name<'a>(func: &'a Function<'a>) -> Option<&Atom<'a>> { None } +fn get_property_key_name<'a>(key: &'a PropertyKey<'a>) -> Option { + match key { + PropertyKey::NullLiteral(_) => Some("null".to_string()), + PropertyKey::RegExpLiteral(regex) => { + Some(format!("/{}/{}", regex.regex.pattern, regex.regex.flags)) + } + PropertyKey::BigIntLiteral(bigint) => Some(bigint.raw.to_string()), + PropertyKey::TemplateLiteral(template) => { + if template.expressions.len() == 0 && template.quasis.len() == 1 { + if let Some(cooked) = &template.quasis[0].value.cooked { + return Some(cooked.to_string()); + } + } + + None + } + _ => None, + } +} + +fn get_static_property_name<'a>(parent_node: Option<&'a AstNode<'a>>) -> Option { + parent_node?; + + let result_key = match parent_node.unwrap().kind() { + AstKind::PropertyDefinition(definition) => Some((&definition.key, definition.computed)), + AstKind::MethodDefinition(method_definition) => { + Some((&method_definition.key, method_definition.computed)) + } + AstKind::ObjectProperty(property) => Some((&property.key, property.computed)), + _ => None, + }; + + result_key?; + + let prop = result_key.unwrap().0; + + if prop.is_identifier() && !result_key.unwrap().1 { + prop.name()?; + + let prop_name = prop.name().unwrap().to_string(); + return Some(prop_name); + } + + get_property_key_name(prop) +} + +/** + * Gets the name and kind of the given function node. + * @see https://github.com/eslint/eslint/blob/48117b27e98639ffe7e78a230bfad9a93039fb7f/lib/rules/utils/ast-utils.js#L1762 + */ +fn get_function_name_with_kind(func: &Function, parent_node: Option<&AstNode>) -> String { + let mut tokens: Vec = vec![]; + + if parent_node.is_some() { + match parent_node.unwrap().kind() { + AstKind::MethodDefinition(definition) => { + if definition.r#static { + tokens.push("static".to_owned()); + } + + if !definition.computed && definition.key.is_private_identifier() { + tokens.push("private".to_owned()); + } + } + AstKind::PropertyDefinition(definition) => { + if definition.r#static { + tokens.push("static".to_owned()); + } + + if !definition.computed && definition.key.is_private_identifier() { + tokens.push("private".to_owned()); + } + } + _ => {} + } + } + + if func.r#async { + tokens.push("async".to_owned()); + } + + if func.generator { + tokens.push("generator".to_owned()); + } + + if parent_node.is_some() { + match parent_node.unwrap().kind() { + AstKind::MethodDefinition(method_definition) => match method_definition.kind { + MethodDefinitionKind::Constructor => tokens.push("constructor".to_owned()), + MethodDefinitionKind::Get => tokens.push("getter".to_owned()), + MethodDefinitionKind::Set => tokens.push("setter".to_owned()), + MethodDefinitionKind::Method => tokens.push("method".to_owned()), + }, + AstKind::PropertyDefinition(_) => tokens.push("method".to_owned()), + _ => { + tokens.push("function".to_owned()); + } + } + + match parent_node.unwrap().kind() { + AstKind::MethodDefinition(method_definition) => { + if !method_definition.computed && method_definition.key.is_private_identifier() { + let name = method_definition.key.name(); + + if let Some(name) = name { + tokens.push(name.to_string()); + } + } + } + AstKind::PropertyDefinition(definition) => { + if !definition.computed && definition.key.is_private_identifier() { + let name = definition.key.name(); + + if let Some(name) = name { + tokens.push(name.to_string()); + } + } else if let Some(static_name) = get_static_property_name(parent_node) { + tokens.push(static_name); + } else if let Some(name) = get_function_name(func) { + tokens.push(name.to_string()); + } + } + _ => { + if let Some(static_name) = get_static_property_name(parent_node) { + tokens.push(static_name); + } else if let Some(name) = get_function_name(func) { + tokens.push(name.to_string()); + } + } + } + } + + tokens.join(" ") +} + fn is_invalid_function( func: &Function, config: &FuncNamesConfig, @@ -230,7 +371,7 @@ impl Rule for FuncNames { }; } fn run_once(&self, ctx: &LintContext<'_>) { - let mut invalid_funcs: Vec<&Function> = vec![]; + let mut invalid_funcs: Vec<(&Function, Option<&AstNode>)> = vec![]; for node in ctx.nodes().iter() { match node.kind() { @@ -241,7 +382,7 @@ impl Rule for FuncNames { if func.generator { &self.generators_config } else { &self.default_config }; if is_invalid_function(func, config, parent_node) { - invalid_funcs.push(func); + invalid_funcs.push((func, parent_node)); } } @@ -267,7 +408,7 @@ impl Rule for FuncNames { }); if let Some(span) = ast_span { - invalid_funcs.retain(|func| func.span != span); + invalid_funcs.retain(|(func, _)| func.span != span); } } } @@ -275,17 +416,18 @@ impl Rule for FuncNames { } } - for func in &invalid_funcs { + for (func, parent_node) in &invalid_funcs { let func_name = get_function_name(func); + let func_name_complete = get_function_name_with_kind(func, *parent_node); if func_name.is_some() { ctx.diagnostic( - OxcDiagnostic::warn(format!("Unexpected named {:?}.", func_name.unwrap())) + OxcDiagnostic::warn(format!("Unexpected named {func_name_complete}.")) .with_label(Span::new(func.span.start, func.params.span.start)), ); } else { ctx.diagnostic( - OxcDiagnostic::warn("Unexpected unnamed.") + OxcDiagnostic::warn(format!("Unexpected unnamed {func_name_complete}.")) .with_label(Span::new(func.span.start, func.params.span.start)), ); } diff --git a/crates/oxc_linter/src/snapshots/func_names.snap b/crates/oxc_linter/src/snapshots/func_names.snap index 7e948716ed2f3..485e02082d16d 100644 --- a/crates/oxc_linter/src/snapshots/func_names.snap +++ b/crates/oxc_linter/src/snapshots/func_names.snap @@ -1,307 +1,307 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function() {}; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:12] 1 │ var test = function(d, e, f) {}; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:5] 1 │ new function() {} · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function() {}; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:2] 1 │ (function(){}()) · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:3] 1 │ f(function(){}) · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ var a = new Date(function() {}); · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:5] 1 │ new function() {} · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:13] 1 │ var {foo} = function(){}; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:18] 1 │ ({ a: obj.prop = function(){} } = foo); · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:13] 1 │ [obj.prop = function(){}] = foo; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ var { a: [b] = function(){} } = foo; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:22] 1 │ function foo({ a } = function(){}) {}; · ──────── ╰──── - ⚠ eslint(func-names): Unexpected named "foo". + ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:9] 1 │ var x = function foo() {}; · ──────────── ╰──── - ⚠ eslint(func-names): Unexpected named "foo". + ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:21] 1 │ Foo.prototype.bar = function foo() {}; · ──────────── ╰──── - ⚠ eslint(func-names): Unexpected named "foo". + ⚠ eslint(func-names): Unexpected named function foo. ╭─[func_names.tsx:1:8] 1 │ ({foo: function foo() {}}) · ──────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ export default function() {} · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:16] 1 │ export default function() {} · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:17] 1 │ export default (function(){}); · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:11] 1 │ var foo = function*() {}; · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *() {}); · ────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed generator function. ╭─[func_names.tsx:1:2] 1 │ (function*() {}()) · ───────── ╰──── - ⚠ eslint(func-names): Unexpected named "baz". + ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── - ⚠ eslint(func-names): Unexpected named "baz". + ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── - ⚠ eslint(func-names): Unexpected named "baz". + ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── - ⚠ eslint(func-names): Unexpected named "baz". + ⚠ eslint(func-names): Unexpected named generator function baz. ╭─[func_names.tsx:1:15] 1 │ var foo = bar(function *baz() {}); · ───────────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed method foo. ╭─[func_names.tsx:1:17] 1 │ class C { foo = function() {} } · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed method. ╭─[func_names.tsx:1:19] 1 │ class C { [foo] = function() {} } · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed private method foo. ╭─[func_names.tsx:1:18] 1 │ class C { #foo = function() {} } · ──────── ╰──── - ⚠ eslint(func-names): Unexpected unnamed. + ⚠ eslint(func-names): Unexpected unnamed function. ╭─[func_names.tsx:1:21] 1 │ class C { foo = bar(function() {}) } · ──────── ╰──── - ⚠ eslint(func-names): Unexpected named "bar". + ⚠ eslint(func-names): Unexpected named method foo. ╭─[func_names.tsx:1:17] 1 │ class C { foo = function bar() {} } · ──────────── From 72c1d43431c529a06c39d91ac6492d0c1279f642 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 21:35:00 +0200 Subject: [PATCH 08/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 135 +++++++----------- 1 file changed, 51 insertions(+), 84 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 1da933befb828..ca25dac0719ed 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -63,16 +63,19 @@ fn is_object_or_class_method(parent_node: Option<&AstNode>) -> bool { return false; } - match parent_node.unwrap().kind() { - AstKind::MethodDefinition(_) => true, - AstKind::ObjectProperty(property) => { - property.method - || property.kind == PropertyKind::Get - || property.kind == PropertyKind::Set - } + let unwrapped_kind = parent_node.unwrap().kind(); - _ => false, + if matches!(unwrapped_kind, AstKind::MethodDefinition(_)) { + return true; } + + if let AstKind::ObjectProperty(property) = unwrapped_kind { + return property.method + || property.kind == PropertyKind::Get + || property.kind == PropertyKind::Set; + } + + false } /** * Determines whether the current FunctionExpression node has a name that would be @@ -83,82 +86,50 @@ fn has_inferred_name(function: &Function, parent_node: Option<&AstNode>) -> bool return true; } - if parent_node.is_none() { - return false; - } - + // unwrap is safe because of is_object_or_class_method match parent_node.unwrap().kind() { AstKind::VariableDeclarator(declarator) => { - if let BindingPatternKind::BindingIdentifier(_) = declarator.id.kind { - if let Expression::FunctionExpression(function_expression) = - declarator.init.as_ref().unwrap() - { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - } - - false + matches!(declarator.id.kind, BindingPatternKind::BindingIdentifier(_)) + && matches!(declarator.init.as_ref().unwrap(), Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::ObjectProperty(property) => { - if let Expression::FunctionExpression(function_expression) = &property.value { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - - false + matches!(&property.value, Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::PropertyDefinition(definition) => { - if let Expression::FunctionExpression(function_expression) = - definition.value.as_ref().unwrap() - { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - - false + matches!(&definition.value.as_ref().unwrap(), Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::AssignmentExpression(expression) => { - if let AssignmentTarget::AssignmentTargetIdentifier(_) = expression.left { - if let Expression::FunctionExpression(function_expression) = &expression.right { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - } - - false + matches!(expression.left, AssignmentTarget::AssignmentTargetIdentifier(_)) + && matches!(&expression.right, Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::AssignmentTargetWithDefault(target) => { - if let AssignmentTarget::AssignmentTargetIdentifier(_) = target.binding { - if let Expression::FunctionExpression(function_expression) = &target.init { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - } - - false + matches!(target.binding, AssignmentTarget::AssignmentTargetIdentifier(_)) + && matches!(&target.init, Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::AssignmentPattern(pattern) => { - if let BindingPatternKind::BindingIdentifier(_) = pattern.left.kind { - if let Expression::FunctionExpression(function_expression) = &pattern.right { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } - } - - false + matches!(pattern.left.kind, BindingPatternKind::BindingIdentifier(_)) + && matches!(&pattern.right, Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) } AstKind::ObjectAssignmentTarget(target) => { for property in &target.properties { - if let AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(identifier) = - property - { - if let Expression::FunctionExpression(function_expression) = - identifier.init.as_ref().unwrap() - { - return get_function_identifier(function_expression) - == get_function_identifier(function); - } + if matches!(property, AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(identifier) + if matches!(identifier.init.as_ref().unwrap(), Expression::FunctionExpression(function_expression) + if get_function_identifier(function_expression) == get_function_identifier(function) + ) + ) { + return true; } } @@ -172,27 +143,22 @@ fn has_inferred_name(function: &Function, parent_node: Option<&AstNode>) -> bool * Gets the identifier for the function */ fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&'a Span> { - if let Some(id) = &func.id { - return Some(&id.span); - } - - None + func.id.as_ref().map(|id| &id.span) } /** * Gets the identifier name of the function */ fn get_function_name<'a>(func: &'a Function<'a>) -> Option<&Atom<'a>> { - if let Some(id) = &func.id { - return Some(&id.name); - } - - None + func.id.as_ref().map(|id| &id.name) } fn get_property_key_name<'a>(key: &'a PropertyKey<'a>) -> Option { + if matches!(key, PropertyKey::NullLiteral(_)) { + return Some("null".to_string()); + } + match key { - PropertyKey::NullLiteral(_) => Some("null".to_string()), PropertyKey::RegExpLiteral(regex) => { Some(format!("/{}/{}", regex.regex.pattern, regex.regex.flags)) } @@ -229,8 +195,7 @@ fn get_static_property_name<'a>(parent_node: Option<&'a AstNode<'a>>) -> Option< if prop.is_identifier() && !result_key.unwrap().1 { prop.name()?; - let prop_name = prop.name().unwrap().to_string(); - return Some(prop_name); + return Some(prop.name().unwrap().to_string()); } get_property_key_name(prop) @@ -276,7 +241,9 @@ fn get_function_name_with_kind(func: &Function, parent_node: Option<&AstNode>) - } if parent_node.is_some() { - match parent_node.unwrap().kind() { + let kind = parent_node.unwrap().kind(); + + match kind { AstKind::MethodDefinition(method_definition) => match method_definition.kind { MethodDefinitionKind::Constructor => tokens.push("constructor".to_owned()), MethodDefinitionKind::Get => tokens.push("getter".to_owned()), @@ -289,7 +256,7 @@ fn get_function_name_with_kind(func: &Function, parent_node: Option<&AstNode>) - } } - match parent_node.unwrap().kind() { + match kind { AstKind::MethodDefinition(method_definition) => { if !method_definition.computed && method_definition.key.is_private_identifier() { let name = method_definition.key.name(); From 51e3e4b574b6c23984c99d71b3e240885a139b8f Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 21:36:06 +0200 Subject: [PATCH 09/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index ca25dac0719ed..08472578cfdd9 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -157,7 +157,7 @@ fn get_property_key_name<'a>(key: &'a PropertyKey<'a>) -> Option { if matches!(key, PropertyKey::NullLiteral(_)) { return Some("null".to_string()); } - + match key { PropertyKey::RegExpLiteral(regex) => { Some(format!("/{}/{}", regex.regex.pattern, regex.regex.flags)) From 4225d9ea37e2a8b04a1328278f6123c8e80c41a0 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sat, 3 Aug 2024 22:24:19 +0200 Subject: [PATCH 10/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 08472578cfdd9..4a30295df9b57 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -203,7 +203,7 @@ fn get_static_property_name<'a>(parent_node: Option<&'a AstNode<'a>>) -> Option< /** * Gets the name and kind of the given function node. - * @see https://github.com/eslint/eslint/blob/48117b27e98639ffe7e78a230bfad9a93039fb7f/lib/rules/utils/ast-utils.js#L1762 + * @see */ fn get_function_name_with_kind(func: &Function, parent_node: Option<&AstNode>) -> String { let mut tokens: Vec = vec![]; @@ -251,26 +251,20 @@ fn get_function_name_with_kind(func: &Function, parent_node: Option<&AstNode>) - MethodDefinitionKind::Method => tokens.push("method".to_owned()), }, AstKind::PropertyDefinition(_) => tokens.push("method".to_owned()), - _ => { - tokens.push("function".to_owned()); - } + _ => tokens.push("function".to_owned()), } match kind { - AstKind::MethodDefinition(method_definition) => { - if !method_definition.computed && method_definition.key.is_private_identifier() { - let name = method_definition.key.name(); - - if let Some(name) = name { - tokens.push(name.to_string()); - } + AstKind::MethodDefinition(method_definition) + if !method_definition.computed && method_definition.key.is_private_identifier() => + { + if let Some(name) = method_definition.key.name() { + tokens.push(name.to_string()); } } AstKind::PropertyDefinition(definition) => { if !definition.computed && definition.key.is_private_identifier() { - let name = definition.key.name(); - - if let Some(name) = name { + if let Some(name) = definition.key.name() { tokens.push(name.to_string()); } } else if let Some(static_name) = get_static_property_name(parent_node) { @@ -299,24 +293,24 @@ fn is_invalid_function( ) -> bool { let func_name = get_function_name(func); - if - // never - (*config == FuncNamesConfig::Never - && func_name.is_some() - && func.r#type != FunctionType::FunctionDeclaration) - // as needed - || (*config == FuncNamesConfig::AsNeeded - && func_name.is_none() - && !has_inferred_name(func, parent_node)) - // always - || (*config == FuncNamesConfig::Always - && func_name.is_none() - && !is_object_or_class_method(parent_node)) - { - return true; + match *config { + FuncNamesConfig::Never + if func_name.is_some() && func.r#type != FunctionType::FunctionDeclaration => + { + true + } + FuncNamesConfig::AsNeeded + if func_name.is_none() && !has_inferred_name(func, parent_node) => + { + true + } + FuncNamesConfig::Always + if func_name.is_none() && !is_object_or_class_method(parent_node) => + { + true + } + _ => false, } - - false } impl Rule for FuncNames { From 198e2b7ceb2871efb9d64d483846ea9e84fe1187 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 4 Aug 2024 15:41:13 +0200 Subject: [PATCH 11/15] feat(linter/eslint): implement func-names --- .../oxc_linter/src/rules/eslint/func_names.rs | 96 ++++++++++++++----- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 4a30295df9b57..ab9cc9f7922ac 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -23,13 +23,21 @@ enum FuncNamesConfig { Never, } -impl FuncNamesConfig { - pub fn from(raw: &str) -> Self { - match raw { - "always" => FuncNamesConfig::Always, - "as-needed" => FuncNamesConfig::AsNeeded, - "never" => FuncNamesConfig::Never, - _ => FuncNamesConfig::default(), +impl TryFrom<&serde_json::Value> for FuncNamesConfig { + type Error = OxcDiagnostic; + + fn try_from(raw: &serde_json::Value) -> Result { + if !raw.is_string() { + return Err(OxcDiagnostic::error(format!("Expecting string, got {raw}"))); + } + + match raw.as_str().unwrap() { + "always" => Ok(FuncNamesConfig::Always), + "as-needed" => Ok(FuncNamesConfig::AsNeeded), + "never" => Ok(FuncNamesConfig::Never), + v => { + Err(OxcDiagnostic::error(format!("Expecting always, as-needed or never, got {v}"))) + } } } } @@ -41,14 +49,57 @@ declare_oxc_lint!( /// /// ### Why is this bad? /// - /// If you leave off the function name then when the function throws an exception you are likely - /// to get something similar to anonymous function in the stack trace. If you provide the optional - /// name for a function expression then you will get the name of the function expression in the stack trace. + /// Leaving the name off a function will cause to appear + /// in stack traces of errorsthrown in it or any function called within it. + /// This makes it more difficult to find where an error is thrown. + /// If you provide the optional name for a function expression + /// then you will get the name of the function expression in the stack trace. /// /// /// ### Example /// + /// Example of **incorrect** code for this rule: + /// + /// ```javascript + /// /*eslint func-names: "error" */ + /// + /// // default is "always" and there is an anonymous function + /// Foo.prototype.bar = function() {}; + /// + /// /*eslint func-names: ["error", "always"] */ + /// + /// // there is an anonymous function + /// Foo.prototype.bar = function() {}; + /// + /// /*eslint func-names: ["error", "as-needed"] */ + /// + /// // there is an anonymous function + /// // where the name isn’t assigned automatically per the ECMAScript specs + /// Foo.prototype.bar = function() {}; + /// + /// /*eslint func-names: ["error", "never"] */ + /// + /// // there is a named function + /// Foo.prototype.bar = function bar() {}; + /// ``` + /// + /// Example of **correct* code for this rule: + /// /// ```javascript + /// /*eslint func-names: "error" */ + /// + /// Foo.prototype.bar = function bar() {}; + /// + /// /*eslint func-names: ["error", "always"] */ + /// /// Foo.prototype.bar = function bar() {}; + /// + /// /*eslint func-names: ["error", "as-needed"] */ + /// + /// var foo = function(){}; + /// + /// /*eslint func-names: ["error", "never"] */ + /// + /// Foo.prototype.bar = function() {}; /// ``` FuncNames, style, @@ -315,21 +366,21 @@ fn is_invalid_function( impl Rule for FuncNames { fn from_configuration(value: serde_json::Value) -> Self { - let obj1 = value.get(0); - let obj2 = value.get(1); + let Some(default_value) = value.get(0) else { + return Self { + default_config: FuncNamesConfig::default(), + generators_config: FuncNamesConfig::default(), + }; + }; - let default_config = - obj1.and_then(serde_json::Value::as_str).map(FuncNamesConfig::from).unwrap_or_default(); + let default_config = FuncNamesConfig::try_from(default_value).unwrap(); - return Self { - default_config: default_config.clone(), + let generators_value = + value.get(1).and_then(|v| v.get("generators")).unwrap_or(default_value); - generators_config: obj2 - .and_then(|v| v.get("generators")) - .and_then(serde_json::Value::as_str) - .map(FuncNamesConfig::from) - .unwrap_or(default_config), - }; + let generators_config = FuncNamesConfig::try_from(generators_value).unwrap(); + + Self { default_config, generators_config } } fn run_once(&self, ctx: &LintContext<'_>) { let mut invalid_funcs: Vec<(&Function, Option<&AstNode>)> = vec![]; @@ -525,7 +576,6 @@ fn test() { ("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 }, - // ("({key: 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 }, From 4a2e63724eb1154a147c132e3136c8087df9c44e Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 4 Aug 2024 17:43:47 +0200 Subject: [PATCH 12/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index ab9cc9f7922ac..0271d96138267 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -28,7 +28,7 @@ impl TryFrom<&serde_json::Value> for FuncNamesConfig { fn try_from(raw: &serde_json::Value) -> Result { if !raw.is_string() { - return Err(OxcDiagnostic::error(format!("Expecting string, got {raw}"))); + return Err(OxcDiagnostic::error(format!("Expecting string for eslint/func-names configuration, got {raw}"))); } match raw.as_str().unwrap() { @@ -36,7 +36,7 @@ impl TryFrom<&serde_json::Value> for FuncNamesConfig { "as-needed" => Ok(FuncNamesConfig::AsNeeded), "never" => Ok(FuncNamesConfig::Never), v => { - Err(OxcDiagnostic::error(format!("Expecting always, as-needed or never, got {v}"))) + Err(OxcDiagnostic::error(format!("Expecting always, as-needed or never for eslint/func-names configuration, got {v}"))) } } } From 711d992abcc2ff204d4db565ffbb9877f2c5f71b Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 4 Aug 2024 18:21:33 +0200 Subject: [PATCH 13/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 0271d96138267..e90aac10f936f 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -28,16 +28,18 @@ impl TryFrom<&serde_json::Value> for FuncNamesConfig { fn try_from(raw: &serde_json::Value) -> Result { if !raw.is_string() { - return Err(OxcDiagnostic::error(format!("Expecting string for eslint/func-names configuration, got {raw}"))); + return Err(OxcDiagnostic::error(format!( + "Expecting string for eslint/func-names configuration, got {raw}" + ))); } match raw.as_str().unwrap() { "always" => Ok(FuncNamesConfig::Always), "as-needed" => Ok(FuncNamesConfig::AsNeeded), "never" => Ok(FuncNamesConfig::Never), - v => { - Err(OxcDiagnostic::error(format!("Expecting always, as-needed or never for eslint/func-names configuration, got {v}"))) - } + v => Err(OxcDiagnostic::error(format!( + "Expecting always, as-needed or never for eslint/func-names configuration, got {v}" + ))), } } } From 3cbdf8e90611d186845bc81e685bd8e5900f8420 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 4 Aug 2024 23:23:04 +0200 Subject: [PATCH 14/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index e90aac10f936f..349d431a2cfa5 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -404,6 +404,16 @@ impl Rule for FuncNames { // when yes remove it from invalid_funcs because recursion are always named AstKind::CallExpression(expression) => { if let Expression::Identifier(identifier) = &expression.callee { + // check at first if the callee calls an invalid function + if invalid_funcs + .iter() + .find(|(func, _)| get_function_name(func) == Some(&identifier.name)) + .is_none() + { + continue; + } + + // a function which is calling itself inside is always valid let ast_span = ctx.nodes().iter_parents(node.id()).skip(1).find_map(|p| { match p.kind() { AstKind::Function(func) => { @@ -421,6 +431,7 @@ impl Rule for FuncNames { } }); + // we found a recursive function, remove it from the invalid list if let Some(span) = ast_span { invalid_funcs.retain(|(func, _)| func.span != span); } From e44034e4f45aec73b517f60e1ecdd50e908367a8 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 4 Aug 2024 23:43:55 +0200 Subject: [PATCH 15/15] feat(linter/eslint): implement func-names --- crates/oxc_linter/src/rules/eslint/func_names.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 349d431a2cfa5..0ea77a934a5bd 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -405,10 +405,9 @@ impl Rule for FuncNames { AstKind::CallExpression(expression) => { if let Expression::Identifier(identifier) = &expression.callee { // check at first if the callee calls an invalid function - if invalid_funcs + if !invalid_funcs .iter() - .find(|(func, _)| get_function_name(func) == Some(&identifier.name)) - .is_none() + .any(|(func, _)| get_function_name(func) == Some(&identifier.name)) { continue; }