From 8f7424f0fcb55e0764c85cfee9e2e04d5d3d60fb Mon Sep 17 00:00:00 2001 From: Sysix Date: Fri, 27 Dec 2024 14:39:28 +0100 Subject: [PATCH] feat(linter): add rule `eslint/new-cap` --- crates/oxc_linter/src/rules/eslint/new_cap.rs | 66 ++++++++++++++----- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/new_cap.rs b/crates/oxc_linter/src/rules/eslint/new_cap.rs index 65e0c1bbcfd43..a25ac093f17bb 100644 --- a/crates/oxc_linter/src/rules/eslint/new_cap.rs +++ b/crates/oxc_linter/src/rules/eslint/new_cap.rs @@ -151,7 +151,7 @@ impl Rule for NewCap { match node.kind() { AstKind::NewExpression(expression) if self.new_is_cap => { let Some(short_name) = - extract_name_from_new_expression(&expression.callee, &node.kind()) + &extract_name_from_new_expression(&expression.callee, &node.kind()) else { return; }; @@ -162,11 +162,12 @@ impl Rule for NewCap { return; }; - let capitalization = get_cap(&short_name); + let capitalization = get_cap(short_name); let allowed = capitalization != GetCapResult::Lower || is_cap_allowed_expression( &expression.callee, + short_name, name, &self.new_is_cap_exceptions, &self.new_is_cap_exception_pattern, @@ -178,7 +179,7 @@ impl Rule for NewCap { } AstKind::CallExpression(expression) if self.cap_is_new => { let Some(short_name) = - extract_name_from_new_expression(&expression.callee, &node.kind()) + &extract_name_from_new_expression(&expression.callee, &node.kind()) else { return; }; @@ -189,7 +190,7 @@ impl Rule for NewCap { return; }; - let capitalization = get_cap(&short_name); + let capitalization = get_cap(short_name); let mut caps_is_new_exceptions = self.cap_is_new_exceptions.clone(); caps_is_new_exceptions.append(&mut caps_allowed_vec()); @@ -197,6 +198,7 @@ impl Rule for NewCap { let allowed = capitalization != GetCapResult::Upper || is_cap_allowed_expression( &expression.callee, + short_name, name, &caps_is_new_exceptions, &self.cap_is_new_exception_pattern, @@ -219,10 +221,13 @@ fn extract_name_deep_from_new_expression( return Some(identifier.name.clone().into()); } - match expression { + match expression.without_parentheses() { Expression::StaticMemberExpression(expression) => { - let obj_name = extract_name_deep_from_new_expression(&expression.object, kind); let prop_name = expression.property.name.clone().into_compact_str(); + let obj_name = extract_name_deep_from_new_expression( + expression.object.without_parentheses(), + kind, + ); if let Some(obj_name) = obj_name { let new_name = format!("{obj_name}.{prop_name}"); @@ -231,6 +236,20 @@ fn extract_name_deep_from_new_expression( Some(prop_name) } + Expression::ComputedMemberExpression(expression) => { + let prop_name = expression.static_property_name()?; + let obj_name = extract_name_deep_from_new_expression( + expression.object.without_parentheses(), + kind, + ); + + if let Some(obj_name) = obj_name { + let new_name = format!("{obj_name}.{prop_name}"); + return Some(CompactStr::new(&new_name)); + } + + Some(prop_name.into_compact_str()) + } _ => get_static_property_name(kind).map(std::convert::Into::into), } } @@ -240,16 +259,29 @@ fn extract_name_from_new_expression(expression: &Expression, kind: &AstKind) -> return Some(identifier.name.clone().into()); } - get_static_property_name(kind).map(std::convert::Into::into) + match expression { + Expression::StaticMemberExpression(expression) => { + Some(expression.property.name.clone().into_compact_str()) + } + Expression::ComputedMemberExpression(expression) => { + expression.static_property_name().map(std::convert::Into::into) + } + _ => get_static_property_name(kind).map(std::convert::Into::into), + } } fn is_cap_allowed_expression( expression: &Expression<'_>, + short_name: &CompactStr, name: &CompactStr, exceptions: &Vec, patterns: &Option, ) -> bool { - if exceptions.contains(name) { + if exceptions.contains(name) || exceptions.contains(short_name) { + return true; + } + + if name == "Date.UTC" { return true; } @@ -297,7 +329,7 @@ fn test() { ("var x = Array(42)", None), ("var x = Boolean(42)", None), ("var x = Date(42)", None), - // ("var x = Date.UTC(2000, 0)", None), + ("var x = Date.UTC(2000, 0)", None), ("var x = Error('error')", None), ("var x = Function('return 0')", None), ("var x = Number(42)", None), @@ -343,17 +375,17 @@ fn test() { // "var x = new foo.bar(42);", // Some(serde_json::json!([{ "newIsCapExceptionPattern": "^foo\\.." }])), // ), - ("var x = new foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), - ("var x = Foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), - ( - "var x = foo.Bar(42);", - Some(serde_json::json!([{ "capIsNew": false, "properties": false }])), - ), + // ("var x = new foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), + // ("var x = Foo.bar(42);", Some(serde_json::json!([{ "properties": false }]))), + // ( + // "var x = foo.Bar(42);", + // Some(serde_json::json!([{ "capIsNew": false, "properties": false }])), + // ), ("foo?.bar();", None), // { "ecmaVersion": 2020 }, ("(foo?.bar)();", None), // { "ecmaVersion": 2020 }, ("new (foo?.Bar)();", None), // { "ecmaVersion": 2020 }, - ("(foo?.Bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, - ("new (foo?.bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, + // ("(foo?.Bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, + // ("new (foo?.bar)();", Some(serde_json::json!([{ "properties": false }]))), // { "ecmaVersion": 2020 }, ("Date?.UTC();", None), // { "ecmaVersion": 2020 }, ("(Date?.UTC)();", None), // { "ecmaVersion": 2020 } ];