From e275659cdc95f77ece3417fef95d0b0b4f3f4538 Mon Sep 17 00:00:00 2001 From: Wang Wenzhe Date: Tue, 28 May 2024 23:04:17 +0800 Subject: [PATCH] feat(linter): add `oxc/no-rest-spread-properties` rule (#3432) People open this rule may have some specific purpose, I am deliberating about whether to add a custom message. --- crates/oxc_linter/src/rules.rs | 2 + .../rules/oxc/no_rest_spread_properties.rs | 164 ++++++++++++++++++ .../snapshots/no_rest_spread_properties.snap | 39 +++++ 3 files changed, 205 insertions(+) create mode 100644 crates/oxc_linter/src/rules/oxc/no_rest_spread_properties.rs create mode 100644 crates/oxc_linter/src/snapshots/no_rest_spread_properties.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index cc0a1e80e..862765aa3 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -351,6 +351,7 @@ mod oxc { pub mod no_async_await; pub mod no_barrel_file; pub mod no_const_enum; + pub mod no_rest_spread_properties; pub mod number_arg_out_of_range; pub mod only_used_in_recursion; pub mod uninvoked_array_callback; @@ -715,6 +716,7 @@ oxc_macros::declare_all_lint_rules! { oxc::const_comparisons, oxc::double_comparisons, oxc::erasing_op, + oxc::no_rest_spread_properties, oxc::misrefactored_assign_op, oxc::missing_throw, oxc::no_accumulating_spread, diff --git a/crates/oxc_linter/src/rules/oxc/no_rest_spread_properties.rs b/crates/oxc_linter/src/rules/oxc/no_rest_spread_properties.rs new file mode 100644 index 000000000..962f1c3e7 --- /dev/null +++ b/crates/oxc_linter/src/rules/oxc/no_rest_spread_properties.rs @@ -0,0 +1,164 @@ +use oxc_ast::{ast::AssignmentTarget, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_rest_spread_properties_diagnostic(span0: Span, x1: &str, x2: &str) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("oxc(no-rest-spread-properties): {x1} are not allowed. {x2}")) + .with_labels([span0.into()]) +} + +#[derive(Debug, Default, Clone)] +pub struct NoRestSpreadProperties(Box); + +#[derive(Debug, Default, Clone)] +pub struct NoRestSpreadPropertiesOptions { + object_spread_message: String, + object_rest_message: String, +} + +impl std::ops::Deref for NoRestSpreadProperties { + type Target = NoRestSpreadPropertiesOptions; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow [Object Rest/Spread Properties](https://github.com/tc39/proposal-object-rest-spread#readme). + /// + /// ### Example + /// + /// ```javascript + /// let { x, ...y } = z; + /// let z = { x, ...y }; + /// ``` + /// + /// ### Options + /// + /// ```json + /// { + /// "rules": { + /// "no-rest-spread-properties": [ + /// "error", + /// { + /// "objectSpreadMessage": "Object spread properties are not allowed.", + /// "objectRestMessage": "Object rest properties are not allowed." + /// } + /// ] + /// } + /// } + /// ``` + /// + /// - `objectSpreadMessage`: A message to display when object spread properties are found. + /// - `objectRestMessage`: A message to display when object rest properties are found. + /// + NoRestSpreadProperties, + restriction, +); + +impl Rule for NoRestSpreadProperties { + fn from_configuration(value: serde_json::Value) -> Self { + let config = value.get(0); + let object_spread_message = config + .and_then(|v| v.get("objectSpreadMessage")) + .and_then(serde_json::Value::as_str) + .unwrap_or_default(); + let object_rest_message = config + .and_then(|v| v.get("objectRestMessage")) + .and_then(serde_json::Value::as_str) + .unwrap_or_default(); + + Self(Box::new(NoRestSpreadPropertiesOptions { + object_spread_message: object_spread_message.to_string(), + object_rest_message: object_rest_message.to_string(), + })) + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::SpreadElement(spread_element) => { + if ctx + .nodes() + .parent_kind(node.id()) + .is_some_and(|parent| matches!(parent, AstKind::ObjectExpression(_))) + { + ctx.diagnostic(no_rest_spread_properties_diagnostic( + spread_element.span, + "object spread property", + self.object_spread_message.as_str(), + )); + } + } + AstKind::BindingRestElement(rest_element) => { + if ctx + .nodes() + .parent_kind(node.id()) + .is_some_and(|parent| matches!(parent, AstKind::ObjectPattern(_))) + { + ctx.diagnostic(no_rest_spread_properties_diagnostic( + rest_element.span, + "object rest property", + self.object_rest_message.as_str(), + )); + } + } + AstKind::AssignmentTarget(assign_target) => { + let AssignmentTarget::ObjectAssignmentTarget(object_assign) = assign_target else { + return; + }; + let Some(rest) = &object_assign.rest else { + return; + }; + + ctx.diagnostic(no_rest_spread_properties_diagnostic( + rest.span, + "object rest property", + self.object_rest_message.as_str(), + )); + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + // test case are copied from eslint-plugin-es: + // https://github.com/mysticatea/eslint-plugin-es/blob/v1.4.1/tests/lib/rules/no-rest-spread-properties.js + let pass = vec![ + ("[...a]", None), + ("[...a] = foo", None), + ("({a: [...b]})", None), + ("({a: [...b]} = obj)", None), + ("function f(...a) {}", None), + ("f(...a)", None), + ]; + + let fail = vec![ + ("({...a})", None), + ("({...a} = obj)", None), + ("for ({...a} in foo) {}", None), + ("function f({...a}) {}", None), + ( + "({...a})", + Some(serde_json::json!([{ + "objectSpreadMessage": "Our codebase does not allow object spread properties." + }])), + ), + ( + "({...a} = obj)", + Some(serde_json::json!([{ + "objectRestMessage": "Our codebase does not allow object rest properties." + }])), + ), + ]; + + Tester::new(NoRestSpreadProperties::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_rest_spread_properties.snap b/crates/oxc_linter/src/snapshots/no_rest_spread_properties.snap new file mode 100644 index 000000000..863c84e77 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_rest_spread_properties.snap @@ -0,0 +1,39 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_rest_spread_properties +--- + ⚠ oxc(no-rest-spread-properties): object spread property are not allowed. + ╭─[no_rest_spread_properties.tsx:1:3] + 1 │ ({...a}) + · ──── + ╰──── + + ⚠ oxc(no-rest-spread-properties): object rest property are not allowed. + ╭─[no_rest_spread_properties.tsx:1:3] + 1 │ ({...a} = obj) + · ──── + ╰──── + + ⚠ oxc(no-rest-spread-properties): object rest property are not allowed. + ╭─[no_rest_spread_properties.tsx:1:7] + 1 │ for ({...a} in foo) {} + · ──── + ╰──── + + ⚠ oxc(no-rest-spread-properties): object rest property are not allowed. + ╭─[no_rest_spread_properties.tsx:1:13] + 1 │ function f({...a}) {} + · ──── + ╰──── + + ⚠ oxc(no-rest-spread-properties): object spread property are not allowed. Our codebase does not allow object spread properties. + ╭─[no_rest_spread_properties.tsx:1:3] + 1 │ ({...a}) + · ──── + ╰──── + + ⚠ oxc(no-rest-spread-properties): object rest property are not allowed. Our codebase does not allow object rest properties. + ╭─[no_rest_spread_properties.tsx:1:3] + 1 │ ({...a} = obj) + · ──── + ╰────